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.
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.
Elementos base indivisibles: Button, Heading, Text, Icon, Input, Badge, Image, Link, Tag, Textarea, ThemeToggle, ReadingProgress.
Grupos de átomos con un solo propósito: Card, NavLink, FormField, Breadcrumbs, SearchBar, LanguageSwitcher, Pagination y más.
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.
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.
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.
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.
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> NavLink: Estado activo consciente del idioma
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>
© {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
- 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.
- <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.
- 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.
- 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.
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 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.
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.
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.
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.
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.
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.
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.
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.
InfoBox, CodeTabs, ComparisonTable, FaqAccordion — componentes específicos del blog en la subcarpeta blog/. Content Collections para posts trilingues. 106 páginas generadas.
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
Un átomo es indivisible — no tiene sentido descomponerlo más. Un boton es un átomo. Un boton con una etiqueta al lado es una molécula. La prueba: Puedo descomponer esto más sin que las partes individuales pierdan su propósito? Si la respuesta es sí, sigue descomponiendo. Si es no, es un átomo.
Los componentes son neutrales al idioma — reciben texto traducido a través de props o slots. El LanguageSwitcher es una excepción: conoce los locales disponibles. Pero incluso el recibe el locale actual como prop en lugar de determinarlo por si mismo. Las traducciones viven en el nivel de página o en Content Collections, no en los componentes.
Para proyectos muy pequenos (menos de 10 componentes), la sobrecarga de la estructura de carpetas supera los beneficios. También para prototipos o proyectos desechables, la jerarquía estricta te frena. Atomic Design brilla en proyectos medianos a grandes que se mantienen y extienden durante meses — y para equipos que necesitan un lenguaje compartido para componentes.
Si un organismo importa más de 3-4 moléculas o excede 150 líneas, verifica si puedes dividirlo. El organismo Header importa Navigation, LanguageSwitcher, ThemeToggle y Button. Son cuatro hijos — aceptable. Pero un organismo "MegaSection" con 8 moléculas sugiere que en realidad necesitas dos organismos.
Sí, pero raramente. La regla principal es: los organismos importan moléculas. Pero a veces un organismo necesita un único Icon o Heading directamente, sin un wrapper de molécula. Eso esta bien — es "raramente átomos" según las reglas. Solo si regularmente usas muchos átomos directamente en organismos, probablemente te faltan algunas moléculas.
Astro no tiene previsualización de componentes integrada como Storybook. Yo creo una ruta oculta /dev (no aparece en la navegación) que renderiza todos los átomos con diversos props. Esto me permite verificar visualmente si los cambios en tokens o código de átomos producen el resultado esperado. Esta ruta solo existe en el entorno de desarrollo.
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
- 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.
- 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.
- 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.
- 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.
- 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:
- Empieza con átomos y tokens — son el fundamento sobre el que todo se construye
- Respeta la regla de dirección — las dependencias fluyen solo hacia abajo
- Usa slots para contenido, props para configuración — esto hace los componentes flexibles sin ser frágiles
- Abstrae en la segunda ocurrencia — no en la primera, no en la tercera
- Organiza componentes específicos del contexto en subcarpetas —
blog/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.