Documentación de UXR SEO Analyzer

Introducción

Imágenes de Texto

Ver contenido

Introducción

WCAG 1.4.5 (Imágenes de Texto) requiere que si las tecnologías utilizadas pueden lograr la presentación visual, se use texto para transmitir información en lugar de imágenes de texto. Esto significa usar texto HTML real con estilos CSS en lugar de incrustar texto dentro de imágenes.

Aunque las imágenes de texto pueden parecer una forma fácil de lograr tipografía específica, crean barreras significativas de accesibilidad y problemas de usabilidad para muchos usuarios.

Qué Requiere WCAG

1.4.5 Imágenes de Texto (Nivel AA)

Si las tecnologías utilizadas pueden lograr la presentación visual, se usa texto para transmitir información en lugar de imágenes de texto.

Excepciones:

  • Personalizable: La imagen puede personalizarse visualmente según los requisitos del usuario
  • Esencial: Una presentación particular del texto es esencial (como logos o marca)

Esto significa:

  • Usar CSS para estilizar texto en lugar de crear texto como imágenes
  • Se permiten logos que contienen texto
  • El texto esencial para transmitir información (como nombres de marca) puede ser imágenes

Quién Se Beneficia

Tipo de Usuario Beneficio
Usuarios con baja visión Pueden agrandar texto sin pixelación
Usuarios de lectores de pantalla El texto se lee naturalmente, no como “imagen”
Usuarios que necesitan alto contraste Pueden aplicar hojas de estilo personalizadas
Usuarios con dislexia Pueden cambiar fuentes para legibilidad
Motores de búsqueda Pueden indexar y entender el contenido
Todos los usuarios El texto carga más rápido, escala mejor

Problemas con Imágenes de Texto

1. No Se Pueden Redimensionar

<!-- MAL: El texto de imagen se pixela cuando se agranda -->
<img src="heading-text.png" alt="Bienvenido a Nuestro Sitio">

<!-- BIEN: El texto real escala suavemente -->
<h1 class="custom-heading">Bienvenido a Nuestro Sitio</h1>

2. No Se Pueden Personalizar

Los usuarios con discapacidades visuales a menudo usan configuraciones del navegador o extensiones para:

  • Cambiar tamaños de fuente
  • Aplicar temas de alto contraste
  • Usar fuentes específicas (como OpenDyslexic)
  • Ajustar espaciado de línea

Las imágenes de texto no pueden responder a estas preferencias del usuario.

3. Lentas para Cargar

<!-- Imagen de texto: 50-200KB por imagen -->
<img src="large-decorative-heading.png" alt="Oferta Especial">

<!-- Texto real con CSS: <1KB -->
<h2 class="decorative-heading">Oferta Especial</h2>

4. Problemas de Accesibilidad

  • Los lectores de pantalla anuncian “imagen” en lugar de leer naturalmente
  • No se puede seleccionar ni copiar
  • Las herramientas de traducción no pueden procesar el texto
  • No se puede buscar con búsqueda en página (Ctrl+F)

Escenarios Comunes

Usos Aceptables de Imágenes de Texto

Logos y Marca

<!-- Aceptable: El logo contiene presentación esencial de marca -->
<img src="company-logo.svg" alt="Acme Corporation">

Diagramas con Texto Esencial

<!-- Aceptable: El texto es parte del significado esencial del diagrama -->
<figure>
  <img src="flowchart.png" alt="Diagrama de flujo mostrando pasos...">
  <figcaption>Figura 1: Flujo de trabajo del proceso de aprobación</figcaption>
</figure>

Usos Inaceptables

Titulares y Texto de Cuerpo

<!-- MAL: Usando imagen para titular -->
<img src="headline.png" alt="Nuestros Servicios">

<!-- BIEN: Texto real con estilo personalizado -->
<h2 class="services-heading">Nuestros Servicios</h2>

Botones y Navegación

<!-- MAL: Botón de imagen -->
<button><img src="submit-button.png" alt="Enviar"></button>

<!-- BIEN: Botón de texto estilizado -->
<button class="submit-btn">Enviar</button>

Secciones de Llamada a la Acción

<!-- MAL: Imagen de banner con texto -->
<img src="sale-banner.png" alt="50% Descuento en Todo - Compra Ahora">

<!-- BIEN: HTML con estilo CSS -->
<section class="sale-banner">
  <h2>50% Descuento en Todo</h2>
  <a href="/tienda" class="cta-button">Compra Ahora</a>
</section>

Alternativas CSS a Imágenes de Texto

Fuentes Personalizadas

/* Usar web fonts para tipografía personalizada */
@font-face {
  font-family: 'CustomHeading';
  src: url('/fonts/custom-heading.woff2') format('woff2');
  font-display: swap;
}

.decorative-heading {
  font-family: 'CustomHeading', serif;
  font-size: 3rem;
}

Efectos de Texto

/* Texto con gradiente */
.gradient-text {
  background: linear-gradient(45deg, #e66465, #9198e5);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

/* Sombra de texto para profundidad */
.shadow-text {
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}

/* Texto con contorno */
.outline-text {
  -webkit-text-stroke: 2px #000;
  color: transparent;
}

Tipografía Responsiva

/* Texto que escala con el viewport */
.hero-heading {
  font-size: clamp(2rem, 5vw, 5rem);
  font-weight: 700;
  letter-spacing: -0.02em;
}

Probando Imágenes de Texto

Prueba Manual Rápida

  1. Haz clic derecho en el texto de la página
  2. Si aparece “Guardar imagen” o “Copiar imagen”, es probablemente una imagen de texto
  3. Intenta seleccionar el texto - si no puedes resaltarlo, puede ser una imagen
  4. Usa “Inspeccionar Elemento” del navegador para verificar si es <img> o texto

Qué Verificar

  • [ ] Los titulares usan elementos <h1>-<h6> reales, no imágenes
  • [ ] El contenido del cuerpo es texto real, no imágenes
  • [ ] Los botones contienen texto, no etiquetas de imagen
  • [ ] Los efectos de texto decorativo usan CSS, no imágenes
  • [ ] Los logos son las únicas imágenes que contienen texto (y tienen alt text apropiado)

Herramientas para Pruebas

  • WAVE: Resalta imágenes y su texto alternativo
  • axe DevTools: Reporta problemas de imágenes de texto
  • DevTools del navegador: Inspeccionar elementos para verificar texto vs imágenes
  • Lector de pantalla: Escuchar lectura natural de texto vs anuncios de “imagen”

Resumen de Mejores Prácticas

Hacer No Hacer
Usar CSS para estilizar texto Crear imágenes con texto
Usar web fonts para tipografía personalizada Capturar texto para fuentes únicas
Estilizar botones con CSS Usar botones de imagen
Usar iconos SVG con texto separado Incrustar texto en iconos
Mantener logos como únicas imágenes con texto Usar imágenes para titulares

Implementación

Implementación de Web Fonts

Integración de Google Fonts

<!-- En <head> -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&display=swap" rel="stylesheet">
/* Uso */
.encabezado-decorativo {
  font-family: 'Playfair Display', Georgia, serif;
  font-weight: 700;
  font-size: 3rem;
}

Fuentes Auto-Alojadas

/* Declaración font-face para fuentes personalizadas */
@font-face {
  font-family: 'FuenteMarca';
  src: url('/fonts/fuentemarca.woff2') format('woff2'),
       url('/fonts/fuentemarca.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap; /* Previene texto invisible durante carga */
}

@font-face {
  font-family: 'FuenteMarca';
  src: url('/fonts/fuentemarca-bold.woff2') format('woff2'),
       url('/fonts/fuentemarca-bold.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* Fuente variable (archivo único, múltiples pesos) */
@font-face {
  font-family: 'FuenteMarcaVariable';
  src: url('/fonts/fuentemarca-variable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

Efectos de Texto CSS

Texto con Gradiente

/* Relleno gradiente en texto */
.texto-gradiente {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent; /* Fallback */
}

/* Gradiente animado */
.texto-gradiente-animado {
  background: linear-gradient(
    90deg,
    #ff6b6b,
    #feca57,
    #48dbfb,
    #ff9ff3,
    #ff6b6b
  );
  background-size: 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: desplazar-gradiente 8s ease infinite;
}

@keyframes desplazar-gradiente {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

Sombras de Texto

/* Sombra sutil para profundidad */
.sombra-sutil {
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Sombra pronunciada */
.sombra-fuerte {
  text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2);
}

/* Texto brillante */
.texto-brillo {
  text-shadow:
    0 0 10px rgba(255, 255, 255, 0.8),
    0 0 20px rgba(255, 255, 255, 0.6),
    0 0 30px rgba(255, 255, 255, 0.4);
}

/* Efecto neón */
.texto-neon {
  color: #fff;
  text-shadow:
    0 0 5px #fff,
    0 0 10px #fff,
    0 0 20px #0ff,
    0 0 30px #0ff,
    0 0 40px #0ff;
}

/* Efecto 3D retro */
.retro-3d {
  color: #f5f5f5;
  text-shadow:
    1px 1px 0 #c4c4c4,
    2px 2px 0 #b4b4b4,
    3px 3px 0 #a4a4a4,
    4px 4px 0 #949494;
}

Trazo/Contorno de Texto

/* Texto con contorno */
.texto-contorno {
  -webkit-text-stroke: 2px #333;
  color: transparent;
}

/* Relleno con trazo */
.trazo-relleno {
  -webkit-text-stroke: 1px #000;
  color: #fff;
}

/* Efecto doble contorno */
.doble-contorno {
  color: #fff;
  -webkit-text-stroke: 3px #000;
  paint-order: stroke fill;
}

Espaciado de Letras y Transformaciones

/* Mayúsculas espaciadas (ideal para encabezados) */
.mayusculas-espaciadas {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-weight: 600;
}

/* Versalitas */
.versalitas {
  font-variant: small-caps;
  letter-spacing: 0.05em;
}

/* Espaciado condensado */
.espaciado-ajustado {
  letter-spacing: -0.02em;
}

Componentes de Tipografía Decorativa

Componente React

import React from 'react';

interface DecorativeTextProps {
  children: React.ReactNode;
  variant: 'gradient' | 'shadow' | 'outline' | 'glow';
  as?: 'h1' | 'h2' | 'h3' | 'h4' | 'span' | 'p';
  className?: string;
}

const variantStyles = {
  gradient: `
    bg-gradient-to-r from-purple-600 to-pink-600
    bg-clip-text text-transparent
  `,
  shadow: `
    text-gray-900
    [text-shadow:_3px_3px_0_rgb(0_0_0_/_20%)]
  `,
  outline: `
    text-transparent
    [-webkit-text-stroke:_2px_currentColor]
    text-gray-900
  `,
  glow: `
    text-white
    [text-shadow:_0_0_10px_rgba(255,255,255,0.8),_0_0_20px_rgba(255,255,255,0.6)]
  `
};

export function DecorativeText({
  children,
  variant,
  as: Component = 'span',
  className = ''
}: DecorativeTextProps) {
  return (
    <Component className={`${variantStyles[variant]} ${className}`}>
      {children}
    </Component>
  );
}

// Uso
<DecorativeText variant="gradient" as="h1" className="text-5xl font-bold">
  Encabezado Hermoso
</DecorativeText>

Componente Vue 3

<script setup lang="ts">
import { computed } from 'vue'

type TextVariant = 'gradient' | 'shadow' | 'outline' | 'glow'

interface Props {
  variant: TextVariant
  as?: string
}

const props = withDefaults(defineProps<Props>(), {
  as: 'span'
})

const variantClasses = computed(() => {
  const variants: Record<TextVariant, string> = {
    gradient: 'decorative-gradient',
    shadow: 'decorative-shadow',
    outline: 'decorative-outline',
    glow: 'decorative-glow'
  }
  return variants[props.variant]
})
</script>

<template>
  <component :is="as" :class="variantClasses">
    <slot />
  </component>
</template>

<style scoped>
.decorative-gradient {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.decorative-shadow {
  text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2);
}

.decorative-outline {
  -webkit-text-stroke: 2px currentColor;
  color: transparent;
}

.decorative-glow {
  color: #fff;
  text-shadow:
    0 0 10px rgba(255, 255, 255, 0.8),
    0 0 20px rgba(255, 255, 255, 0.6);
}
</style>

Convirtiendo Imágenes de Texto a CSS

Antes: Banner de Imagen

<!-- ANTIGUO: Imagen con texto incrustado -->
<img src="banner-oferta.png" alt="Oferta de Verano - Hasta 50% de Descuento - Compra Ahora">

Después: Banner CSS

<section class="banner-oferta">
  <div class="banner-contenido">
    <p class="banner-subtitulo">Tiempo Limitado</p>
    <h2 class="banner-titulo">Oferta de Verano</h2>
    <p class="banner-descuento">Hasta <span class="resaltado">50% de Descuento</span></p>
    <a href="/ofertas" class="banner-cta">Compra Ahora</a>
  </div>
</section>
.banner-oferta {
  background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
  padding: 3rem;
  text-align: center;
  border-radius: 1rem;
}

.banner-subtitulo {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: 0.875rem;
  color: rgba(255, 255, 255, 0.9);
  margin-bottom: 0.5rem;
}

.banner-titulo {
  font-family: 'Playfair Display', serif;
  font-size: clamp(2rem, 5vw, 4rem);
  color: #fff;
  margin-bottom: 0.5rem;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}

.banner-descuento {
  font-size: 1.5rem;
  color: #fff;
}

.resaltado {
  font-weight: 700;
  font-size: 2rem;
}

.banner-cta {
  display: inline-block;
  margin-top: 1.5rem;
  padding: 1rem 2rem;
  background: #fff;
  color: #ff6b6b;
  font-weight: 600;
  border-radius: 2rem;
  text-decoration: none;
  transition: transform 0.2s, box-shadow 0.2s;
}

.banner-cta:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

Texto SVG para Efectos Complejos

<!-- Texto SVG con efectos no posibles en CSS -->
<svg viewBox="0 0 400 100" class="encabezado-svg" aria-labelledby="titulo-svg">
  <title id="titulo-svg">Tipografía Creativa</title>
  <defs>
    <linearGradient id="gradiente-texto" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:#667eea"/>
      <stop offset="100%" style="stop-color:#764ba2"/>
    </linearGradient>
    <filter id="sombra">
      <feDropShadow dx="2" dy="2" stdDeviation="2" flood-opacity="0.3"/>
    </filter>
  </defs>
  <text
    x="50%"
    y="50%"
    dominant-baseline="middle"
    text-anchor="middle"
    fill="url(#gradiente-texto)"
    filter="url(#sombra)"
    font-family="Georgia, serif"
    font-size="48"
    font-weight="bold"
  >
    Tipografía Creativa
  </text>
</svg>
.encabezado-svg {
  width: 100%;
  max-width: 400px;
  height: auto;
}

/* El texto SVG hereda la fuente de CSS si no se especifica */
.encabezado-svg text {
  font-family: inherit;
}

Pruebas Automatizadas

Pruebas Playwright para Imágenes de Texto

import { test, expect } from '@playwright/test';

test.describe('Cumplimiento de Imágenes de Texto', () => {
  test('ninguna imagen contiene texto excepto logos', async ({ page }) => {
    await page.goto('/');

    // Obtener todas las imágenes
    const images = page.locator('img');
    const count = await images.count();

    for (let i = 0; i < count; i++) {
      const img = images.nth(i);
      const alt = await img.getAttribute('alt') || '';
      const src = await img.getAttribute('src') || '';
      const className = await img.getAttribute('class') || '';

      // Omitir imágenes de logo (aceptables)
      const esLogo = className.includes('logo') ||
                     src.includes('logo') ||
                     alt.toLowerCase().includes('logo');

      if (!esLogo) {
        // Verificar si el texto alt sugiere que es contenido de texto
        const tieneContenidoTexto = /^[A-Z]/.test(alt) && // Comienza con mayúscula
                                    alt.length > 20 && // Suficientemente largo para ser una oración
                                    !alt.includes('foto') &&
                                    !alt.includes('imagen') &&
                                    !alt.includes('ilustración');

        if (tieneContenidoTexto) {
          console.warn(`Posible imagen de texto: ${src} - Alt: "${alt}"`);
        }
      }
    }
  });

  test('los encabezados son elementos de texto reales', async ({ page }) => {
    await page.goto('/');

    // Todos los h1-h6 deben ser texto real, no imágenes
    const encabezados = page.locator('h1, h2, h3, h4, h5, h6');
    const count = await encabezados.count();

    for (let i = 0; i < count; i++) {
      const encabezado = encabezados.nth(i);
      const innerHTML = await encabezado.innerHTML();

      // No debe contener etiquetas img
      expect(innerHTML).not.toMatch(/<img/i);

      // Debe tener contenido de texto real
      const textContent = await encabezado.textContent();
      expect(textContent?.trim().length).toBeGreaterThan(0);
    }
  });

  test('los botones contienen texto no imágenes', async ({ page }) => {
    await page.goto('/');

    const botones = page.locator('button, [role="button"], .btn');
    const count = await botones.count();

    for (let i = 0; i < count; i++) {
      const boton = botones.nth(i);
      const innerHTML = await boton.innerHTML();

      // Permitir iconos SVG, pero no etiquetas img para texto
      const tieneEtiquetaImg = /<img[^>]*alt=["'][^"']{10,}["']/i.test(innerHTML);

      if (tieneEtiquetaImg) {
        console.warn('El botón puede contener imagen de texto:', innerHTML);
      }

      // Debe tener nombre accesible (texto o aria-label)
      const textContent = await boton.textContent();
      const ariaLabel = await boton.getAttribute('aria-label');

      expect(textContent?.trim() || ariaLabel).toBeTruthy();
    }
  });

  test('el texto decorativo usa CSS no imágenes', async ({ page }) => {
    await page.goto('/');

    // Verificar elementos que podrían ser texto estilizado
    const elementosEstilizados = page.locator('.texto-gradiente, .decorativo, .texto-elegante, [class*="efecto-texto"]');
    const count = await elementosEstilizados.count();

    for (let i = 0; i < count; i++) {
      const elemento = elementosEstilizados.nth(i);
      const tagName = await elemento.evaluate(el => el.tagName);

      // No debe ser img
      expect(tagName).not.toBe('IMG');
    }
  });
});

Lista de Verificación Resumen

  • [ ] Todo el contenido de texto usa elementos HTML de texto real
  • [ ] Las fuentes personalizadas usan @font-face o servicios de web fonts
  • [ ] Los efectos de texto se logran con CSS (gradientes, sombras, contornos)
  • [ ] Los banners y CTAs usan HTML/CSS, no imágenes
  • [ ] Solo los logos contienen imágenes de texto esenciales
  • [ ] El texto SVG incluye title/aria-label apropiados
  • [ ] El texto escala correctamente al hacer zoom
  • [ ] El texto se puede seleccionar y copiar
  • [ ] Los lectores de pantalla leen el texto naturalmente

Artículos Relacionados


Referencias

  1. W3C - WCAG 2.2 SC 1.4.5 Images of Text
  2. W3C - C22 Using CSS to control visual presentation of text
  3. WebAIM - Alternative Text
  4. MDN - Web fonts
  5. W3C - G140 Separating information and structure from presentation
  6. MDN - CSS Text
  7. Google Fonts - Using web fonts

Artículos relacionados

Hub de categoría

Hub

Hub de Cumplimiento WCAG 2.2

La accesibilidad web asegura que los sitios web y aplicaciones puedan ser utilizados por todos, incluyendo personas con discapacidades

Última actualización: