Librería de componentes con más de 45 componentes Astro en Atomic Design
15 min read

Atomic Design en la práctica: 45+ componentes Astro

#Atomic Design #Astro #Component Architecture

Conoces la teoria detras de Atomic Design — átomos, moléculas, organismos, templates, páginas. Todos hemos leido sobre ello, dibujado los diagramas y asentido con la cabeza. Pero, qué sucede cuando realmente implementas esta metodología en un proyecto real? No en un tutorial con tres componentes, sino en un portfolio multilingüe con más de 45 componentes, 3 layouts y 106 páginas generadas.

Eso es exactamente lo que hice. Y aprendi cosas que ningún artículo teórico te contara. Este post te muestra las decisiones concretas, la estructura real de carpetas y los patrones que demostraron su valor a lo largo de meses de trabajo productivo.

Atomic Design suena simple en la teoria. El verdadero desafío comienza cuando tienes que decidir si algo es un átomo o una molécula — y tienes que tomar esa decision 45 veces seguidas.

Arnold Wender Desarrollador Web & Creador Digital

Los números: Lo que realmente se construyó

Antes de sumergirnos en el código, aquí tienes un resumen de lo que la aplicación consistente de Atomic Design a un proyecto real produjo en la práctica.

45+
Componentes
6
Categorías de Tokens
3
Layouts
106
Páginas
85%
Reutilización
Átomos (12)

Elementos base indivisibles: Button, Heading, Text, Icon, Input, Badge, Image, Link, Tag, Textarea, ThemeToggle, ReadingProgress.

Moléculas (17)

Grupos de átomos con un solo propósito: Card, NavLink, FormField, Breadcrumbs, SearchBar, LanguageSwitcher, Pagination y más.

Organismos (16)

Secciones autocontenidas: Header, Footer, Hero, Contact, Portfolio, Services, Testimonials, Navigation y componentes del blog.

Estos números no son casualidad. La distribución — muchos átomos y moléculas, menos organismos — refleja la piramide natural que emerge de la aplicación consistente de Atomic Design. La base es ancha (muchas piezas pequenas y reutilizables), la cima es estrecha (pocas secciones grandes y especializadas). Si tu arquitectura se ve invertida — muchos organismos, pocos átomos — algo anda mal con tus abstracciones.

La estructura real de carpetas

Los artículos teóricos te muestran una estructura de carpetas prolija con cinco directorios y tres archivos cada uno. La realidad es diferente. Aquí esta la estructura real de mi portfolio — cada archivo individual.

src/components/atoms/
├── Badge.astro          # Etiquetas de color para categorias y estado
├── Button.astro         # Primario/Secundario/Ghost, <a> o <button>
├── Heading.astro        # h1–h6, tamano independiente del nivel
├── Icon.astro           # 68+ rutas SVG en un componente
├── Image.astro          # Carga diferida, aspect ratio, fallback
├── Input.astro          # Text/email/search con validacion
├── Link.astro           # Enlace interno/externo con soporte de icono
├── Tag.astro            # Tags clicables de filtro
├── Text.astro           # Texto con variantes de tamano y peso
├── Textarea.astro       # Campo de entrada multilinea
├── ThemeToggle.astro    # Interruptor modo oscuro/claro
└── blog/
    └── ReadingProgress.astro  # Barra de progreso de lectura
src/components/molecules/
├── Breadcrumbs.astro        # Navegacion de ruta + Schema.org
├── Card.astro               # Tarjeta universal: Heading + Text + Button
├── ContactInfo.astro        # Bloque de direccion/telefono/email
├── FormField.astro          # Label + Input + mensaje de error
├── LanguageSwitcher.astro   # Selector de idioma DE/EN/ES
├── NavLink.astro            # Enlace de navegacion con estado activo
├── Pagination.astro         # Navegacion de paginas con prev/next
├── ProjectCard.astro        # Tarjeta de portfolio con tags
├── SearchBar.astro          # Input + Button + logica de busqueda
├── SocialLinks.astro        # Enlaces con iconos a redes sociales
├── TestimonialCard.astro    # Testimonio de cliente con avatar
├── TimelineItem.astro       # Entrada individual de linea temporal
├── TrustBadge.astro         # Indicadores de confianza
└── blog/
    ├── CalloutBox.astro     # Caja CTA dentro de articulos
    ├── InfoBox.astro        # Caja de nota/advertencia/consejo
    ├── QuoteBlock.astro     # Cita con atribucion
    └── StatCard.astro       # Metrica con etiqueta
src/components/organisms/
├── About.astro              # Seccion sobre mi
├── Contact.astro            # Formulario de contacto completo
├── FaqSection.astro         # FAQ con acordeon
├── Footer.astro             # Footer completo
├── Header.astro             # Header con navegacion
├── Hero.astro               # Seccion hero con variantes
├── Navigation.astro         # Navegacion principal (escritorio/movil)
├── Portfolio.astro          # Galeria de proyectos
├── Pricing.astro            # Vista general de precios
├── Services.astro           # Vista general de servicios
├── Testimonials.astro       # Carrusel de testimonios
├── TimelineSection.astro    # Linea temporal de experiencia
└── blog/
    ├── CodeTabs.astro       # Tabs de codigo para MDX
    ├── ComparisonTable.astro # Tabla comparativa
    ├── FaqAccordion.astro   # FAQ dentro de articulos del blog
    └── ...                  # Organismos de blog adicionales
src/layouts/
├── BaseLayout.astro         # Base: Head + Header + Main + Footer
├── BlogLayout.astro         # Especifico del blog: TOC + Author + Related
└── PortfolioLayout.astro    # Detalle portfolio: Galeria + Stack tecnico

src/pages/
├── index.astro              # Pagina principal (Hero + Services + Portfolio + ...)
├── about.astro              # Sobre mi
├── contact.astro            # Contacto
├── pricing.astro            # Precios
├── blog/
│   ├── index.astro          # Vista general del blog
│   ├── [slug].astro         # Post individual
│   ├── page/[page].astro    # Paginacion
│   ├── tags.astro           # Vista general de tags
│   ├── tag/[tag].astro      # Posts por tag
│   ├── categories.astro     # Vista general de categorias
│   ├── kategorie/[cat].astro # Posts por categoria
│   └── search.astro         # Busqueda del blog
└── portfolio/
    ├── index.astro          # Vista general del portfolio
    └── [slug].astro         # Detalle del portfolio

Observa las subcarpetas blog/ dentro de atoms/, molecules/ y organisms/. Los componentes específicos del blog como InfoBox, CodeTabs o ReadingProgress viven alli, separados de los componentes generales de UI. Esto evita que las carpetas principales se saturen con componentes específicos del contexto y deja claro que partes solo tienen sentido en el contexto del blog.

Átomos que lo impulsan todo

Los átomos son los cimientos de tu sistema de diseño. Si un átomo esta mal disenado, lo sientes en cada componente por encima de el. Aquí tienes cuatro átomos que construi con especial cuidado — y por que.

Componente Icon.astro — Editor de código con interface Props y biblioteca de iconos con 12 previsualizaciones SVG
Icon.astro — Más de 68 SVGs en un componente

En lugar de gestionar 68 archivos de iconos separados, Icon.astro almacena todas las rutas SVG en un único switch statement. El átomo acepta un prop name del tipo IconName (un union type de TypeScript) y renderiza la ruta SVG correspondiente. El tamaño y el color se controlan a través de props. Este único átomo es importado por NavLink, Button, SocialLinks, ContactInfo y docenas de otros componentes. Un cambio en el sistema de iconos tiene efecto inmediato en todas partes.

Componente Button.astro — Editor de código con interface Props y tres variantes de boton: Primary, Secondary, Ghost
Button.astro — HTML semántico a través de Props

Button.astro renderiza un elemento <a> (cuando existe un prop href) o un elemento <button> — decidido en tiempo de compilación. Además: variant (primary/secondary/ghost), size (sm/md/lg) y un prop icon opcional. El atributo class:list de Astro combina clases CSS condicionalmente. Un único átomo que cubre tanto navegación como formularios sin sacrificar HTML semántico.

Componente Heading.astro — Jerarquía visual de h1 a h6 con niveles semánticos y tamanos visuales desacoplados
Heading.astro — Semántica y visual desacoplados

La trampa más común en sistemas de diseño: h2 siempre se ve como h2. Heading.astro separa el level (h1-h6, para semántica y accesibilidad) del size visual (sm/md/lg/xl/2xl). Así, un <h3> semántico puede mostrarse tan grande como un <h1> — por ejemplo, cuando una sección esta profundamente anidada pero el título necesita ser prominente.

Componente Text.astro — Editor de código con Props y catalogo visual: grande negrita, base regular, pequeño atenuado
Text.astro — Tipografía consistente en todas partes

Todo el texto del proyecto pasa por Text.astro. Props: size (sm/base/lg), weight (normal/medium/bold), muted (booleano para texto secundario) y un prop as para el elemento HTML (p/span/div). Los design tokens para tamaño de fuente, altura de línea y color aseguran que un cambio tipográfico en tokens/typography.css actualice instantaneamente todo el proyecto.

Icon.astro en detalle

Dado que Icon.astro es utilizado por prácticamente todos los demas componentes, su diseño es especialmente importante. Aquí tienes una vista simplificada de su estructura:

---
/* Atomo Icon: Renderiza SVGs basados en un prop name */
import type { IconName } from '../../types';

interface Props {
  name: IconName;
  size?: number;
  class?: string;
  label?: string;    /* Para accesibilidad, cuando el icono esta solo */
}

const { name, size = 24, class: className, label } = Astro.props;

/* aria-hidden cuando es puramente decorativo, aria-label cuando es significativo */
const isDecorative = !label;
---

<svg
  xmlns="http://www.w3.org/2000/svg"
  width={size}
  height={size}
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
  class:list={['icon', className]}
  aria-hidden={isDecorative}
  aria-label={label}
  role={label ? 'img' : undefined}
>
  {/* 68+ rutas SVG via switch(name) */}
</svg>
---
/* Molecula NavLink: Usa el atomo Icon para icono opcional */
import Icon from '../atoms/Icon.astro';
import Link from '../atoms/Link.astro';

interface Props {
  href: string;
  icon?: IconName;
  isActive?: boolean;
  locale?: 'de' | 'en' | 'es';
}
---

<Link
  href={href}
  class:list={['nav-link', { 'nav-link--active': isActive }]}
>
  {icon && <Icon name={icon} size={18} />}
  <slot />
</Link>

Moléculas: Componiendo átomos

Una molécula es un grupo de átomos que juntos cumplen un único propósito. Esa es la prueba clave: si puedes describir el grupo como “es un X”, es una molécula. “Es una tarjeta.” “Es un enlace de navegación.” “Es una barra de búsqueda.”

Card = Heading + Text + Button + Icon

El componente Card demuestra perfectamente cómo funcionan las moléculas. Importa cuatro átomos y los organiza en un layout semántico:

---
/* Molecula Card: Compone atomos en una tarjeta reutilizable */
import Heading from '../atoms/Heading.astro';
import Text from '../atoms/Text.astro';
import Button from '../atoms/Button.astro';
import Icon from '../atoms/Icon.astro';

interface Props {
  title: string;
  description: string;
  icon?: IconName;
  href?: string;
  buttonText?: string;
}

const { title, description, icon, href, buttonText } = Astro.props;
---

<article class="card">
  {icon && (
    <div class="card__icon">
      <Icon name={icon} size={32} />
    </div>
  )}
  <Heading level={3} size="md">{title}</Heading>
  <Text size="sm" muted>{description}</Text>
  {buttonText && href && (
    <Button variant="ghost" href={href} size="sm">
      {buttonText}
    </Button>
  )}
</article>
---
/* Molecula Breadcrumbs: Enlaces con markup Schema.org */
import Link from '../atoms/Link.astro';
import Icon from '../atoms/Icon.astro';

interface Props {
  items: BreadcrumbItem[];
}

const { items } = Astro.props;
---

<nav aria-label="Ruta de navegacion" class="breadcrumbs">
  <ol itemscope itemtype="https://schema.org/BreadcrumbList">
    {items.map((item, index) => (
      <li itemprop="itemListElement" itemscope
          itemtype="https://schema.org/ListItem">
        {item.href ? (
          <Link href={item.href} itemprop="item">
            <span itemprop="name">{item.label}</span>
          </Link>
        ) : (
          <span itemprop="name" aria-current="page">
            {item.label}
          </span>
        )}
        <meta itemprop="position" content={String(index + 1)} />
        {index < items.length - 1 && (
          <Icon name="chevron-right" size={14} />
        )}
      </li>
    ))}
  </ol>
</nav>

<!-- Schema.org BreadcrumbList JSON-LD se genera automaticamente -->
---
/* Molecula FormField: Label + Input + mensaje de error como unidad */
import Input from '../atoms/Input.astro';
import Text from '../atoms/Text.astro';

interface Props {
  label: string;
  name: string;
  type?: 'text' | 'email' | 'tel';
  required?: boolean;
  error?: string;
  helpText?: string;
}

const { label, name, type = 'text', required, error, helpText } = Astro.props;
const inputId = `field-${name}`;
---

<div class:list={['form-field', { 'form-field--error': error }]}>
  <label for={inputId} class="form-field__label">
    {label}
    {required && <span aria-hidden="true">*</span>}
  </label>
  <Input
    type={type}
    name={name}
    id={inputId}
    required={required}
    aria-describedby={error ? `${inputId}-error` : helpText ? `${inputId}-help` : undefined}
    aria-invalid={!!error}
  />
  {error && (
    <Text id={`${inputId}-error`} size="sm" class="form-field__error" role="alert">
      {error}
    </Text>
  )}
  {helpText && !error && (
    <Text id={`${inputId}-help`} size="sm" muted>
      {helpText}
    </Text>
  )}
</div>

Una molécula particularmente interesante en un proyecto multilingüe: NavLink necesita saber si la ruta actual coincide con el enlace — teniendo en cuenta el prefijo del locale. /en/blog y /de/blog son ambos “Blog”, pero solo uno debe estar marcado como activo.

Organismos: Los pesos pesados

Los organismos son secciones autocontenidas que se sienten como partes reales de un sitio web. Un header es un organismo. Un footer es un organismo. Una sección hero es un organismo. Orquestan moléculas y determinan su layout.

---
/* Organismo Header: Orquesta navegacion, selector de idioma, tema */
import NavLink from '../molecules/NavLink.astro';
import LanguageSwitcher from '../molecules/LanguageSwitcher.astro';
import ThemeToggle from '../atoms/ThemeToggle.astro';
import Navigation from './Navigation.astro';
import Icon from '../atoms/Icon.astro';
import Button from '../atoms/Button.astro';

interface Props {
  locale?: 'de' | 'en' | 'es';
  currentPath: string;
}

const { locale = 'de', currentPath } = Astro.props;
const navItems = SITE.navigation;
---

<header class="header" id="header">
  <div class="header__inner">
    <!-- Logo -->
    <a href={`/${locale}`} class="header__logo" aria-label="Ir a la pagina principal">
      <Icon name="logo" size={32} />
    </a>

    <!-- Navegacion de escritorio -->
    <Navigation items={navItems} locale={locale} currentPath={currentPath} />

    <!-- Area de acciones -->
    <div class="header__actions">
      <LanguageSwitcher locale={locale} currentPath={currentPath} />
      <ThemeToggle />
      <Button variant="primary" size="sm" href={`/${locale}/contact`}>
        Contacto
      </Button>
    </div>

    <!-- Toggle de menu movil -->
    <button class="header__menu-toggle" aria-label="Abrir menu">
      <Icon name="menu" size={24} />
    </button>
  </div>
</header>
---
/* Organismo Footer: Compuesto de Heading, Text, SocialLinks */
import Heading from '../atoms/Heading.astro';
import Text from '../atoms/Text.astro';
import Link from '../atoms/Link.astro';
import SocialLinks from '../molecules/SocialLinks.astro';

interface Props {
  locale?: 'de' | 'en' | 'es';
}

const { locale = 'de' } = Astro.props;
const currentYear = new Date().getFullYear();

/* Enlaces del footer especificos por idioma */
const footerNav = SITE.footerNavigation[locale];
---

<footer class="footer" id="footer">
  <div class="footer__inner">
    <div class="footer__brand">
      <Heading level={2} size="md">Arnold Wender</Heading>
      <Text muted>Desarrollador Web & Creador Digital</Text>
      <SocialLinks />
    </div>

    <nav class="footer__nav" aria-label="Navegacion del footer">
      {footerNav.columns.map((column) => (
        <div class="footer__column">
          <Heading level={3} size="sm">{column.title}</Heading>
          <ul>
            {column.links.map((link) => (
              <li>
                <Link href={link.href}>{link.label}</Link>
              </li>
            ))}
          </ul>
        </div>
      ))}
    </nav>

    <div class="footer__bottom">
      <Text size="sm" muted>
        &copy; {currentYear} Arnold Wender. Todos los derechos reservados.
      </Text>
    </div>
  </div>
</footer>
---
/* Organismo Hero: Hero flexible con variantes centrada/izquierda */
import Heading from '../atoms/Heading.astro';
import Text from '../atoms/Text.astro';
import Button from '../atoms/Button.astro';
import Badge from '../atoms/Badge.astro';

interface Props {
  title: string;
  subtitle?: string;
  badge?: string;
  variant?: 'centered' | 'left';
  primaryCta?: { text: string; href: string };
  secondaryCta?: { text: string; href: string };
}

const {
  title,
  subtitle,
  badge,
  variant = 'centered',
  primaryCta,
  secondaryCta,
} = Astro.props;
---

<section class:list={['hero', `hero--${variant}`]} id="hero">
  <div class="hero__content">
    {badge && <Badge variant="accent">{badge}</Badge>}
    <Heading level={1} size="2xl">{title}</Heading>
    {subtitle && <Text size="lg" class="hero__subtitle">{subtitle}</Text>}
    <div class="hero__actions">
      {primaryCta && (
        <Button variant="primary" size="lg" href={primaryCta.href}>
          {primaryCta.text}
        </Button>
      )}
      {secondaryCta && (
        <Button variant="secondary" size="lg" href={secondaryCta.href}>
          {secondaryCta.text}
        </Button>
      )}
    </div>
  </div>
  <slot />  {/* Para contenido visual opcional */}
</section>

Cómo Hero.astro habilita variantes flexibles

Observa el prop variant en Hero.astro. En lugar de construir dos componentes separados (HeroCentered y HeroLeft), un único componente usa class:list para variar el layout. El CSS en el bloque <style> contiene los modificadores .hero--centered y .hero--left. Un organismo, dos apariencias, un único punto de mantenimiento.

La única regla: Las dependencias fluyen solo hacia abajo

Si hay una regla que te llevas de Atomic Design, es esta: Nunca importes hacia arriba. Los átomos no importan nada. Las moléculas importan solo átomos. Los organismos importan moléculas (y raramente átomos). Los layouts componen organismos. Las páginas usan layouts.

Qué sucede cuando rompes esta regla? Aquí tienes una comparación:

Aspecto Dirección correcta Recommended Regla rota
Dirección de import Molécula importa Átomo Átomo importa Molécula
Dependencias Claras y lineales Circulares e impredecibles
Cambio individual Se propaga hacia arriba — controlado Se propaga en todas direcciones — caos
Errores de build Localizables inmediatamente Fallos en cascada entre niveles
Testeabilidad Átomo testeable aislado Átomo necesita Molécula necesita Organismo
Refactorización Reemplazo en un nivel Todo debe cambiar simultaneamente
Nuevo desarrollador Entiende la jerarquía al instante Debe entender todo el sistema

Un ejemplo concreto

Imagina que tu átomo Button.astro importa Card.astro (una molécula) porque el boton quiere mostrar una referencia a una tarjeta como tooltip. Ahora Button depende de Card, pero Card ya importa Button. Dependencia circular. Astro podría compilar igualmente, pero has creado un sistema frágil dónde un cambio en Card puede romper Button — y con ello todos los demas componentes que usan Button.

La solución: El tooltip se convierte en su propia molécula (Tooltip.astro) que importa Button y Text. La tarjeta usa Tooltip, no al reves.

Design Tokens como el pegamento

Los design tokens son el pegamento invisible entre todos los niveles. Un cambio de color en :root fluye automáticamente a través de cada átomo, molécula y organismo. Sin actualización manual de 45 componentes.

/* src/styles/tokens/colors.css */
:root {
  /* Color primario — un cambio aqui actualiza todo el proyecto */
  --color-primary-500: #6366f1;
  --color-primary-600: #4f46e5;
  --color-primary-700: #4338ca;

  /* Tokens semanticos — mapeados a tokens base */
  --color-link: var(--color-primary-500);
  --color-focus-ring: var(--color-primary-500);
  --color-btn-primary-bg: var(--color-primary-600);
  --color-btn-primary-hover: var(--color-primary-700);
}

/* Modo oscuro — solo sobrescribir valores semanticos */
[data-theme="dark"] {
  --color-primary-500: #818cf8;
  --color-btn-primary-bg: var(--color-primary-500);
}
/* Button.astro <style> — Atomo usa token */
.btn--primary {
  background-color: var(--color-btn-primary-bg);
  color: var(--color-text-on-primary);
}

.btn--primary:hover {
  background-color: var(--color-btn-primary-hover);
}

.btn:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
/* Card.astro <style> — Molecula usa los mismos tokens */
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-6);
}

.card:hover {
  border-color: var(--color-primary-500);
  box-shadow: var(--shadow-md);
}
/* Hero.astro <style> — Organismo usa los mismos tokens */
.hero {
  padding: var(--space-section-lg) var(--space-4);
  background: var(--color-bg);
}

.hero__subtitle {
  color: var(--color-text-secondary);
  max-width: 60ch;
}

.hero__actions {
  display: flex;
  gap: var(--space-4);
  margin-top: var(--space-8);
}

Patrones de composición en Astro

Astro ofrece cuatro patrones particularmente útiles para Atomic Design que uso de manera consistente a lo largo de este proyecto. Cada patron resuelve un problema específico en la composición de componentes.

Los 4 patrones de composición

  1. class:list para clases dinámicas

    El class:list de Astro acepta arrays, objetos y strings. Ideal para variantes: class:list={['btn', `btn--${variant}`, { 'btn--disabled': disabled }]}. No necesitas ningún paquete classnames — Astro lo maneja nativamente. Usalo en cada átomo para modificadores condicionales.

  2. <slot /> para contenido flexible

    Los slots son el mecanismo de inyección de contenido de Astro. En lugar de pasar contenido como props (propenso a errores con HTML), usa <slot /> para todo lo que sea más que un simple string. Los named slots (<slot name="header" />) habilitan multiples puntos de inyección. El átomo Hero usa un slot por defecto para contenido visual opcional.

  3. Interface Props de TypeScript

    Cada componente define un interface Props {} en el frontmatter. Esto proporciona autocompletado en el IDE, errores en tiempo de compilación para props incorrectos, y documentación viva. Props opcionales con defaults: const { variant = 'primary' } = Astro.props; — haciendo el default explicito y el tipo automáticamente correcto.

  4. Renderizado condicional de tags

    Button.astro renderiza <a> o <button> basado en el prop href. El elemento HTML se decide en tiempo de compilación: const Tag = href ? 'a' : 'button'. Luego: <Tag class:list={classes} {...attrs}>. Esto asegura HTML semántico sin necesitar dos componentes separados.

Por qué los slots son mejores que los props para layout

Un error común en sistemas de diseño: querer resolver todo a través de props. Imagina un InfoBox que debe aceptar un título, descripción y una lista de enlaces. Con props:

<InfoBox title="Nota" description="Considera los siguientes puntos:" links={[...]} />

Esto funciona — hasta que alguien quiere poner un enlace en negrita o insertar un icono. Con slots:

<InfoBox variant="tip" title="Nota">
  Considera los siguientes puntos:

  - **Primer punto** con [un enlace](/)
  - Segundo punto con un <Icon name="check" size={16} /> icono
</InfoBox>

Los slots son más flexibles porque el contenido puede contener markup arbitrario. Los props son mejores para datos estructurados (título, variante, tamaño). La regla general: Props para configuración, slots para contenido.

Lecciones desde las trincheras

Después de meses de trabajar con Atomic Design en un proyecto real de producción — aquí están las lecciones más importantes que ningún artículo teórico te contara.

Empieza con los átomos

Construye los átomos primero. Resiste la tentación de empezar con la sección hero. Cuando tus átomos son solidos, las moléculas y organismos prácticamente se construyen solos.

No sobre-abstraigas

No todos los <div> necesitan ser un átomo. Si algo solo se usa en un único lugar y no necesita props, no necesita su propio componente. Abstrae solo en la segunda ocurrencia.

Slots antes que Props para Layout

Props para configuración (variant, size, disabled). Slots para contenido (texto, componentes anidados, markup). Los slots son más flexibles y se rompen con menos frecuencia.

Un componente, un trabajo

Si un componente hace dos cosas diferentes, dividelo. FormField no válida — solo muestra el estado de error. La lógica de validación vive en una utilidad separada.

Design Tokens como fundamento

Define tokens antes de construir el primer componente. Colores, espaciado, tamanos de fuente, radios, sombras. Todo en :root. Cada componente referencia solo tokens, nunca valores crudos.

Subcarpetas para el contexto

Los componentes específicos del contexto (blog InfoBox, blog CodeTabs) van en una subcarpeta. Esto previene conflictos de nombres y señala claramente: este componente solo tiene sentido en el contexto del blog.

La regla de las dos ocurrencias

Una regla que me ahorro cientos de horas: No crees una abstracción en la primera ocurrencia. Si necesitas un estilo de boton específico, hardcodealo la primera vez. Cuando lo necesites por segunda vez, extrae el átomo. Por que? En la primera ocurrencia, aun no sabes que props vas a necesitar. En la segunda, el patron es claro.

Excepción: Átomos básicos como Button, Heading, Text, Icon — sabes desde el principio que los necesitaras en todas partes. Empieza con esos.

Definición de átomos y design tokens

Button, Heading, Text, Icon, Input, Badge — los bloques de construcción. En paralelo: tokens/colors.css, typography.css, spacing.css. Ni un solo valor hardcodeado desde el principio.

Composición de moléculas

Card, NavLink, FormField, Breadcrumbs, SearchBar — compuestas a partir de los átomos existentes. Aquí es dónde la estabilidad de los átomos fue puesta a prueba. Algunos props necesitaron extensión.

Construcción de organismos y layouts

Header, Footer, Hero, Contact, Portfolio — las grandes secciones. BaseLayout como marco para todo. Aquí es dónde el trabajo previo en los átomos dio frutos: el ensamblaje fue rápido.

Sistema de blog y componentes MDX

InfoBox, CodeTabs, ComparisonTable, FaqAccordion — componentes específicos del blog en la subcarpeta blog/. Content Collections para posts trilingues. 106 páginas generadas.

Iterar y refactorizar

Nuevos requisitos revelaron props faltantes. El átomo Tag se creo cuando lo necesite por tercera vez. El LanguageSwitcher fue promovido de átomo a molécula cuando necesito importar Icon y Link.

FAQ — Preguntas frecuentes

Componentes tradicionales vs. Atomic Design

Aspecto Tradicional Atomic Design Recommended
Estructura de carpetas Plana o por feature Jerárquica por nivel de abstracción
Reutilización Ad-hoc, duplicación frecuente Sistemática, 85%+ de reuso
Consistencia Se desvía con el tiempo Forzada por cascada de tokens
Onboarding Cada proyecto es diferente Lenguaje universal (átomo/molécula/organismo)
Refactorización Efectos secundarios impredecibles Propagación controlada hacia arriba
Modo oscuro / theming Implementado por componente Resuelto una vez a nivel de tokens
Testeabilidad Depende de la profundidad Cada nivel testeable de forma aislada

Introducir Atomic Design en un nuevo proyecto

Guía de implementación

  1. Definir Design Tokens

    Comienza con tokens/colors.css, typography.css y spacing.css. Establece todo el lenguaje visual antes de construir el primer componente. Usa la rejilla de 8 puntos como base para todos los espaciados.

  2. Construir átomos principales

    Button, Heading, Text, Icon, Input — los cinco átomos que garantizadamente necesitaras en todas partes. Cada uno con interface Props en TypeScript, class:list para variantes y exclusivamente referencias a tokens en CSS.

  3. Componer primeras moléculas

    Card, NavLink, FormField — las primeras combinaciones de átomos. Aquí es dónde descubres si los props de tus átomos son lo suficientemente flexibles. Ajusta según sea necesario.

  4. Construir organismos como secciones

    Header, Hero, Footer — las grandes secciones de página que orquestan moléculas. Cada organismo obtiene estructura HTML semántica y landmarks ARIA.

  5. Conectar layouts y páginas

    BaseLayout como marco que envuelve todos los organismos. Las páginas usan layouts y pasan datos. Aquí se cierra el círculo: Token, Átomo, Molécula, Organismo, Layout, Página.

Conclusión

Atomic Design no es un ejercicio academico — es un sistema concreto que resuelve problemas reales. Este portfolio con sus más de 45 componentes, 3 layouts y 106 páginas sería un desastre inmantenible sin la jerarquía clara. Las conclusiones clave:

  1. Empieza con átomos y tokens — son el fundamento sobre el que todo se construye
  2. Respeta la regla de dirección — las dependencias fluyen solo hacia abajo
  3. Usa slots para contenido, props para configuración — esto hace los componentes flexibles sin ser frágiles
  4. Abstrae en la segunda ocurrencia — no en la primera, no en la tercera
  5. Organiza componentes específicos del contexto en subcarpetasblog/ mantiene las carpetas principales limpias

El resultado: Un sistema dónde cambio un color en un archivo de tokens y todo el proyecto se actualiza de manera consistente. Un sistema dónde nuevas páginas se ensamblan en minutos en lugar de horas, porque los bloques de construcción ya existen y encajan entre si.