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

Atomic Design en la práctica: Un portfolio con más de 45 componentes Astro

#Atomic Design #Astro #Component Architecture

Conoces la teoria detras de Atomic Design — atomos, moleculas, organismos, templates, paginas. Todos hemos leido sobre ello, dibujado los diagramas y asentido con la cabeza. Pero, que sucede cuando realmente implementas esta metodologia en un proyecto real? No en un tutorial con tres componentes, sino en un portfolio multilingue con mas de 45 componentes, 3 layouts y 106 paginas generadas.

Eso es exactamente lo que hice. Y aprendi cosas que ningun articulo teorico 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 desafio comienza cuando tienes que decidir si algo es un atomo o una molecula — y tienes que tomar esa decision 45 veces seguidas.

Arnold Wender Desarrollador Web & Creador Digital

Los numeros: Lo que realmente se construyo

Antes de sumergirnos en el codigo, aqui tienes un resumen de lo que la aplicacion consistente de Atomic Design a un proyecto real produjo en la practica.

12
Atomos
17
Moleculas
16
Organismos
3
Layouts
106
Paginas

Estos numeros no son casualidad. La distribucion — muchos atomos y moleculas, menos organismos — refleja la piramide natural que emerge de la aplicacion 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 atomos — algo anda mal con tus abstracciones.

La estructura real de carpetas

Los articulos teoricos te muestran una estructura de carpetas prolija con cinco directorios y tres archivos cada uno. La realidad es diferente. Aqui 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 especificos 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 especificos del contexto y deja claro que partes solo tienen sentido en el contexto del blog.

Atomos que lo impulsan todo

Los atomos son los cimientos de tu sistema de diseno. Si un atomo esta mal disenado, lo sientes en cada componente por encima de el. Aqui tienes cuatro atomos que construi con especial cuidado — y por que.

Componente Icon.astro — Editor de codigo con interface Props y biblioteca de iconos con 12 previsualizaciones SVG
Icon.astro — Mas de 68 SVGs en un componente

En lugar de gestionar 68 archivos de iconos separados, Icon.astro almacena todas las rutas SVG en un unico switch statement. El atomo acepta un prop name del tipo IconName (un union type de TypeScript) y renderiza la ruta SVG correspondiente. El tamano y el color se controlan a traves de props. Este unico atomo 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 codigo con interface Props y tres variantes de boton: Primary, Secondary, Ghost
Button.astro — HTML semantico a traves de Props

Button.astro renderiza un elemento <a> (cuando existe un prop href) o un elemento <button> — decidido en tiempo de compilacion. Ademas: variant (primary/secondary/ghost), size (sm/md/lg) y un prop icon opcional. El atributo class:list de Astro combina clases CSS condicionalmente. Un unico atomo que cubre tanto navegacion como formularios sin sacrificar HTML semantico.

Componente Heading.astro — Jerarquia visual de h1 a h6 con niveles semanticos y tamanos visuales desacoplados
Heading.astro — Semantica y visual desacoplados

La trampa mas comun en sistemas de diseno: h2 siempre se ve como h2. Heading.astro separa el level (h1-h6, para semantica y accesibilidad) del size visual (sm/md/lg/xl/2xl). Asi, un <h3> semantico puede mostrarse tan grande como un <h1> — por ejemplo, cuando una seccion esta profundamente anidada pero el titulo necesita ser prominente.

Componente Text.astro — Editor de codigo con Props y catalogo visual: grande negrita, base regular, pequeno atenuado
Text.astro — Tipografia 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 tamano de fuente, altura de linea y color aseguran que un cambio tipografico en tokens/typography.css actualice instantaneamente todo el proyecto.

Icon.astro en detalle

Dado que Icon.astro es utilizado por practicamente todos los demas componentes, su diseno es especialmente importante. Aqui 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>

Moleculas: Componiendo atomos

Una molecula es un grupo de atomos que juntos cumplen un unico proposito. Esa es la prueba clave: si puedes describir el grupo como “es un X”, es una molecula. “Es una tarjeta.” “Es un enlace de navegacion.” “Es una barra de busqueda.”

Card = Heading + Text + Button + Icon

El componente Card demuestra perfectamente como funcionan las moleculas. Importa cuatro atomos y los organiza en un layout semantico:

---
/* 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 molecula particularmente interesante en un proyecto multilingue: 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 seccion hero es un organismo. Orquestan moleculas 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>

Como Hero.astro habilita variantes flexibles

Observa el prop variant en Hero.astro. En lugar de construir dos componentes separados (HeroCentered y HeroLeft), un unico 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 unico punto de mantenimiento.

La unica regla: Las dependencias fluyen solo hacia abajo

Si hay una regla que te llevas de Atomic Design, es esta: Nunca importes hacia arriba. Los atomos no importan nada. Las moleculas importan solo atomos. Los organismos importan moleculas (y raramente atomos). Los layouts componen organismos. Las paginas usan layouts.

Que sucede cuando rompes esta regla? Aqui tienes una comparacion:

Aspecto Direccion correcta Recommended Regla rota
Direccion de import Molecula importa Atomo Atomo importa Molecula
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 Atomo testeable aislado Atomo necesita Molecula necesita Organismo
Refactorizacion Reemplazo en un nivel Todo debe cambiar simultaneamente
Nuevo desarrollador Entiende la jerarquia al instante Debe entender todo el sistema

Un ejemplo concreto

Imagina que tu atomo Button.astro importa Card.astro (una molecula) 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 podria compilar igualmente, pero has creado un sistema fragil donde un cambio en Card puede romper Button — y con ello todos los demas componentes que usan Button.

La solucion: El tooltip se convierte en su propia molecula (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 automaticamente a traves de cada atomo, molecula y organismo. Sin actualizacion 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 composicion en Astro

Astro ofrece cuatro patrones particularmente utiles para Atomic Design que uso de manera consistente a lo largo de este proyecto. Cada patron resuelve un problema especifico en la composicion de componentes.

Los 4 patrones de composicion

  1. class:list para clases dinamicas

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

  2. <slot /> para contenido flexible

    Los slots son el mecanismo de inyeccion de contenido de Astro. En lugar de pasar contenido como props (propenso a errores con HTML), usa <slot /> para todo lo que sea mas que un simple string. Los named slots (<slot name="header" />) habilitan multiples puntos de inyeccion. El atomo 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 compilacion para props incorrectos, y documentacion viva. Props opcionales con defaults: const { variant = 'primary' } = Astro.props; — haciendo el default explicito y el tipo automaticamente 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 compilacion: const Tag = href ? 'a' : 'button'. Luego: <Tag class:list={classes} {...attrs}>. Esto asegura HTML semantico sin necesitar dos componentes separados.

Por que los slots son mejores que los props para layout

Un error comun en sistemas de diseno: querer resolver todo a traves de props. Imagina un InfoBox que debe aceptar un titulo, descripcion 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 mas flexibles porque el contenido puede contener markup arbitrario. Los props son mejores para datos estructurados (titulo, variante, tamano). La regla general: Props para configuracion, slots para contenido.

Lecciones desde las trincheras

Despues de meses de trabajar con Atomic Design en un proyecto real de produccion — aqui estan las lecciones mas importantes que ningun articulo teorico te contara.

Empieza con los atomos

Construye los atomos primero. Resiste la tentacion de empezar con la seccion hero. Cuando tus atomos son solidos, las moleculas y organismos practicamente se construyen solos.

No sobre-abstraigas

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

Slots antes que Props para Layout

Props para configuracion (variant, size, disabled). Slots para contenido (texto, componentes anidados, markup). Los slots son mas flexibles y se rompen con menos frecuencia.

Un componente, un trabajo

Si un componente hace dos cosas diferentes, dividelo. FormField no valida — solo muestra el estado de error. La logica de validacion 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 especificos del contexto (blog InfoBox, blog CodeTabs) van en una subcarpeta. Esto previene conflictos de nombres y senala 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 abstraccion en la primera ocurrencia. Si necesitas un estilo de boton especifico, hardcodealo la primera vez. Cuando lo necesites por segunda vez, extrae el atomo. Por que? En la primera ocurrencia, aun no sabes que props vas a necesitar. En la segunda, el patron es claro.

Excepcion: Atomos basicos como Button, Heading, Text, Icon — sabes desde el principio que los necesitaras en todas partes. Empieza con esos.

Definicion de atomos y design tokens

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

Composicion de moleculas

Card, NavLink, FormField, Breadcrumbs, SearchBar — compuestas a partir de los atomos existentes. Aqui es donde la estabilidad de los atomos fue puesta a prueba. Algunos props necesitaron extension.

Construccion de organismos y layouts

Header, Footer, Hero, Contact, Portfolio — las grandes secciones. BaseLayout como marco para todo. Aqui es donde el trabajo previo en los atomos dio frutos: el ensamblaje fue rapido.

Sistema de blog y componentes MDX

InfoBox, CodeTabs, ComparisonTable, FaqAccordion — componentes especificos del blog en la subcarpeta blog/. Content Collections para posts trilingues. 106 paginas generadas.

Iterar y refactorizar

Nuevos requisitos revelaron props faltantes. El atomo Tag se creo cuando lo necesite por tercera vez. El LanguageSwitcher fue promovido de atomo a molecula cuando necesito importar Icon y Link.

FAQ — Preguntas frecuentes

Frequently Asked Questions

Conclusion

Atomic Design no es un ejercicio academico — es un sistema concreto que resuelve problemas reales. Este portfolio con sus mas de 45 componentes, 3 layouts y 106 paginas seria un desastre inmantenible sin la jerarquia clara. Las conclusiones clave:

  1. Empieza con atomos y tokens — son el fundamento sobre el que todo se construye
  2. Respeta la regla de direccion — las dependencias fluyen solo hacia abajo
  3. Usa slots para contenido, props para configuracion — esto hace los componentes flexibles sin ser fragiles
  4. Abstrae en la segunda ocurrencia — no en la primera, no en la tercera
  5. Organiza componentes especificos del contexto en subcarpetasblog/ mantiene las carpetas principales limpias

El resultado: Un sistema donde cambio un color en un archivo de tokens y todo el proyecto se actualiza de manera consistente. Un sistema donde nuevas paginas se ensamblan en minutos en lugar de horas, porque los bloques de construccion ya existen y encajan entre si.