Un símbolo pequeño con más matices de los que parece
La marca de verificación, el popular check, aparece en todas partes en la web: listas de características de un plan de precios, pasos completados de un formulario, tareas terminadas, validaciones de contraseñas, tablas comparativas o mensajes de confirmación. Es uno de esos símbolos que cualquier usuario entiende al instante, sin necesidad de traducción.
Sin embargo, no existe una tecla para escribirlo y, si se inserta de cualquier manera, puede mostrarse como un recuadro vacío en algunos dispositivos, verse con un aspecto muy distinto según el sistema operativo o resultar invisible para los lectores de pantalla. Elegir la forma correcta de incluirlo en una página web requiere conocer un poco de Unicode, de HTML, de CSS y de accesibilidad.
En este artículo vamos a recorrer todas las opciones disponibles para mostrar un check en una página: los caracteres Unicode y sus variantes, las entidades HTML numéricas y con nombre, el uso desde CSS con pseudoelementos y desde JavaScript, las diferencias entre los glifos de texto y los emojis, y las alternativas basadas en SVG. Terminaremos con las buenas prácticas de accesibilidad y los errores más habituales, como los temidos signos de interrogación o recuadros vacíos.
Unicode en dos minutos
Unicode es el estándar que asigna un número único a cada carácter de prácticamente todos los sistemas de escritura del mundo, además de miles de símbolos y emojis. Ese número se llama punto de código y se escribe con el prefijo U+ seguido de su valor en hexadecimal.
La marca de verificación básica tiene el punto de código U+2713, cuyo nombre oficial es CHECK MARK. Cuando una página la incluye, el navegador busca en las fuentes disponibles un glifo, es decir, un dibujo, para representar ese carácter.
Para que todo funcione, el documento debe declarar una codificación capaz de representar cualquier carácter Unicode. La opción estándar es UTF-8, que se declara al principio del head:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<title>Lista de tareas</title>
</head>
<body>
<p>Informe enviado ✓</p>
</body>
</html>
En UTF-8, el carácter ✓ ocupa tres bytes. Puedes comprobarlo rápidamente con Python:
import unicodedata
simbolo = "✓"
print(hex(ord(simbolo)), unicodedata.name(simbolo), simbolo.encode("utf-8"))
0x2713 CHECK MARK b'\xe2\x9c\x93'
Con la codificación UTF-8 declarada y el archivo guardado también en UTF-8, puedes escribir el símbolo directamente en el código fuente, pegándolo como cualquier otro carácter.
Las variantes del check en Unicode
Unicode no tiene un único check, sino una pequeña familia de caracteres relacionados, cada uno con su propio aspecto y significado. Estos son los más útiles en desarrollo web:
import unicodedata
for codigo in [0x2713, 0x2714, 0x1F5F8, 0x2705, 0x2611, 0x2610]:
caracter = chr(codigo)
print(f"U+{codigo:04X} {caracter} {unicodedata.name(caracter)}")
U+2713 ✓ CHECK MARK
U+2714 ✔ HEAVY CHECK MARK
U+1F5F8 🗸 LIGHT CHECK MARK
U+2705 ✅ WHITE HEAVY CHECK MARK
U+2611 ☑ BALLOT BOX WITH CHECK
U+2610 ☐ BALLOT BOX
La marca básica U+2713 es la opción más neutra y la que mejor se integra con el texto. La versión gruesa U+2714 tiene un trazo más marcado y resulta útil cuando se necesita más peso visual. U+2705 es un emoji: un check blanco sobre un recuadro verde, con aspecto de icono en color. U+2611 representa una casilla marcada, y U+2610 la casilla vacía equivalente, ideales para simular listas de tareas en texto plano.
Un caso que conviene aclarar es el símbolo de raíz cuadrada, U+221A, que se parece a un check y a veces se usa como tal. Es un carácter matemático y los lectores de pantalla lo anuncian como “raíz cuadrada”, así que no debe usarse para indicar que algo está completado.
En la práctica, junto al check casi siempre se necesita su opuesto para indicar lo que no está incluido o lo que ha fallado:
U+2717 ✗ BALLOT X
U+2718 ✘ HEAVY BALLOT X
U+274C ❌ CROSS MARK
U+2612 ☒ BALLOT BOX WITH X
Elegir parejas coherentes, como ✓ con ✗ o ✔ con ✘, mantiene la consistencia visual de la interfaz.
Entidades HTML: numéricas y con nombre
Si no quieres o no puedes escribir el carácter directamente en el código, HTML permite representarlo mediante referencias de carácter, conocidas habitualmente como entidades. Existen tres formas equivalentes para el check básico:
<!-- Referencia numérica decimal -->
<p>Pago confirmado ✓</p>
<!-- Referencia numérica hexadecimal -->
<p>Pago confirmado ✓</p>
<!-- Entidad con nombre -->
<p>Pago confirmado ✓</p>
Las tres producen exactamente el mismo resultado. La forma decimal usa el valor del punto de código en base diez, la hexadecimal lo usa en base dieciséis precedido de x, y la forma con nombre utiliza un alias legible definido por el estándar HTML.
Las entidades HTML con nombre son más fáciles de recordar y de leer en el código, pero solo existen para algunos caracteres. Para la familia del check y sus opuestos, las más útiles son estas:
<ul>
<li>✓ o ✓ → ✓ (U+2713)</li>
<li>✗ → ✗ (U+2717)</li>
</ul>
Para el resto de variantes no hay nombre, así que se recurre a las referencias numéricas:
<p>✔ Envío gratuito</p>
<!-- ✔ -->
<p>✅ Tarea completada</p>
<!-- ✅ -->
<p>☑ Términos aceptados</p>
<!-- ☑ -->
<p>✘ Sin soporte</p>
<!-- ✘ -->
Una pregunta habitual es cuándo usar el carácter directo y cuándo la entidad. Con UTF-8, escribir el carácter es perfectamente válido y hace el código más legible. Las entidades resultan útiles cuando el entorno en el que se edita o se procesa el código no maneja bien Unicode, cuando se quiere dejar explícito en el código qué carácter se está usando, o cuando el carácter es difícil de distinguir visualmente de otros similares.
Puedes verificar cómo se interpretan las entidades con el módulo html de Python:
import html
print(html.unescape("✓ ✓ ✗ ✔ ✅"))
✓ ✓ ✗ ✔ ✅
Glifos de texto frente a emojis
Una de las fuentes de confusión más comunes es que algunos caracteres pueden mostrarse de dos formas: como un símbolo de texto, que hereda el color y la fuente del texto que lo rodea, o como un emoji en color, con el estilo propio de cada sistema operativo.
El check gordo U+2714 es un buen ejemplo. Por defecto suele mostrarse como texto, pero puede forzarse su presentación como emoji añadiendo un carácter invisible llamado selector de variación 16, U+FE0F. A la inversa, el selector de variación 15, U+FE0E, solicita la presentación como texto:
<p>Presentación de texto: ✔︎</p>
<p>Presentación de emoji: ✔️</p>
El carácter U+2705, en cambio, se muestra como emoji de forma predeterminada en prácticamente todos los sistemas actuales.
Esta diferencia tiene consecuencias prácticas importantes:
- Un glifo de texto respeta la propiedad
colorde CSS, por lo que puedes pintarlo de verde, de gris o del color de tu marca. - Un emoji ignora la propiedad
colory se dibuja con los colores definidos por el sistema, que además cambian de un dispositivo a otro: el mismo emoji se ve distinto en Windows, macOS, Android o iOS. - Los emojis tienen proporciones de icono y pueden desalinear ligeramente el texto si se mezclan en la misma línea.
Como regla general, para interfaces cuidadas, donde el control del color y del tamaño importa, conviene usar los glifos de texto ✓ o ✔, o bien un icono SVG. Los emojis encajan mejor en contextos informales, como mensajes, redes sociales o contenido editorial.
Si necesitas trabajar con estos caracteres desde JavaScript, recuerda que los emojis fuera del plano básico ocupan dos unidades de código UTF-16, lo que afecta a la propiedad length:
console.log("✓".length); // 1
console.log("✅".length); // 1
console.log("🗸".length); // 2: fuera del plano multilingüe básico
console.log([..."🗸"].length); // 1: el spread itera por puntos de código
Mostrar checks desde CSS
En muchas interfaces, el check no forma parte del contenido, sino de la decoración: viñetas de una lista de características, indicadores de pasos completados o marcas en elementos seleccionados. En esos casos, lo más limpio es añadirlo desde CSS, sin tocar el HTML.
La propiedad content de los pseudoelementos ::before y ::after acepta caracteres Unicode escritos con una barra invertida seguida del código hexadecimal:
.lista-ventajas {
list-style: none;
padding-left: 0;
}
.lista-ventajas li {
position: relative;
padding-left: 1.75em;
margin-bottom: 0.5em;
}
.lista-ventajas li::before {
content: "\2713";
position: absolute;
left: 0;
color: #16a34a;
font-weight: 700;
}
<ul class="lista-ventajas">
<li>Almacenamiento ilimitado</li>
<li>Soporte prioritario</li>
<li>Copias de seguridad diarias</li>
</ul>
El pseudoelemento before en CSS es la técnica más extendida, pero no la única. Las listas pueden usar también el pseudoelemento ::marker, que permite cambiar directamente la viñeta nativa sin posicionamiento manual, o la propiedad list-style-type con una cadena:
.lista-simple {
list-style-type: "✓ ";
}
.lista-simple li::marker {
color: #16a34a;
font-weight: 700;
}
El pseudoelemento ::marker solo admite un conjunto limitado de propiedades, como el color, la fuente y el contenido, pero es suficiente para la mayoría de las listas de verificación y tiene la ventaja de mantener la semántica y la alineación nativas de la lista.
Un patrón muy útil es combinar el check y la cruz según un atributo de datos, por ejemplo en una tabla comparativa de planes:
.caracteristica::before {
display: inline-block;
width: 1.25em;
font-weight: 700;
}
.caracteristica[data-incluida="true"]::before {
content: "\2713";
color: #16a34a;
}
.caracteristica[data-incluida="false"]::before {
content: "\2717";
color: #dc2626;
}
<p class="caracteristica" data-incluida="true">Dominio personalizado</p>
<p class="caracteristica" data-incluida="false">Analítica avanzada</p>
Si después de la secuencia de escape aparece un carácter que podría interpretarse como hexadecimal, como un número o una letra entre la A y la F, hay que separarlo con un espacio o completar el código con seis dígitos para evitar que se mezclen: content: "\2713 Listo" o content: "\002713Listo".
Personalizar y animar un check dibujado con CSS
Cuando se necesita un control total sobre el grosor, el tamaño o la animación, otra opción es dibujar el check con bordes de CSS, sin depender de ningún carácter ni fuente:
.check-css {
display: inline-block;
width: 0.45em;
height: 0.9em;
border-right: 0.15em solid currentColor;
border-bottom: 0.15em solid currentColor;
transform: rotate(45deg);
margin-right: 0.5em;
}
Al usar currentColor, el check hereda automáticamente el color del texto. Esta técnica es muy habitual en casillas de verificación personalizadas.
Por ejemplo, para dar estilo a una casilla nativa sin perder su accesibilidad, se puede ocultar su apariencia por defecto con appearance: none y dibujar el check cuando está marcada:
input[type="checkbox"].casilla {
appearance: none;
width: 1.25rem;
height: 1.25rem;
border: 2px solid #64748b;
border-radius: 0.25rem;
display: inline-grid;
place-content: center;
cursor: pointer;
}
input[type="checkbox"].casilla::after {
content: "\2713";
font-size: 0.9rem;
color: white;
transform: scale(0);
transition: transform 120ms ease-in-out;
}
input[type="checkbox"].casilla:checked {
background: #16a34a;
border-color: #16a34a;
}
input[type="checkbox"].casilla:checked::after {
transform: scale(1);
}
input[type="checkbox"].casilla:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
Para casos más sencillos, la propiedad accent-color permite cambiar el color de la casilla nativa con una sola línea, sin rediseñarla:
input[type="checkbox"] {
accent-color: #16a34a;
}
Insertar checks con JavaScript
Cuando el contenido se genera dinámicamente, también es sencillo insertar el símbolo desde JavaScript. Hay varias formas de escribirlo en una cadena:
const directo = "✓";
const escape = "\u2713";
const porCodigo = String.fromCodePoint(0x2713);
const emoji = "\u{1F5F8}"; // sintaxis con llaves para puntos de código altos
console.log(directo === escape && escape === porCodigo); // true
Al actualizar el DOM, conviene usar textContent en lugar de innerHTML para insertar texto, ya que es más seguro frente a inyecciones de código y no interpreta entidades:
function marcarCompletado(elemento) {
const marca = document.createElement("span");
marca.className = "marca";
marca.textContent = "✓";
marca.setAttribute("aria-hidden", "true");
elemento.prepend(marca);
elemento.setAttribute(
"aria-label",
`${elemento.textContent.trim()}, completado`
);
}
document.querySelectorAll('[data-estado="ok"]').forEach(marcarCompletado);
En frameworks como React, el carácter puede escribirse directamente en el JSX. Las entidades HTML también funcionan dentro del JSX, aunque no dentro de cadenas de JavaScript:
function Paso({ titulo, completado }) {
return (
<li className={completado ? "paso paso--ok" : "paso"}>
<span aria-hidden="true">{completado ? "✓" : "○"}</span>
{titulo}
{completado && <span className="sr-only"> (completado)</span>}
</li>
);
}
Alternativas con SVG e iconos
Los caracteres Unicode tienen una limitación: su aspecto depende de la fuente disponible en el sistema del usuario. Si buscas un resultado idéntico en todos los dispositivos, con un trazo concreto y la posibilidad de animarlo, los iconos SVG de check son la opción más robusta:
<svg
class="icono-check"
viewBox="0 0 24 24"
width="20"
height="20"
aria-hidden="true"
focusable="false"
>
<path
d="M5 12.5l4.5 4.5L19 7.5"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Con stroke="currentColor", el icono adopta el color del texto, igual que un glifo. Y como es un trazo vectorial, se puede animar su dibujo con unas pocas líneas de CSS, un efecto muy habitual en confirmaciones:
.icono-check path {
stroke-dasharray: 24;
stroke-dashoffset: 24;
animation: dibujar 400ms ease-out forwards;
}
@keyframes dibujar {
to {
stroke-dashoffset: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.icono-check path {
animation: none;
stroke-dashoffset: 0;
}
}
La regla prefers-reduced-motion respeta la preferencia de los usuarios que han pedido al sistema reducir las animaciones.
Las librerías de iconos como Lucide, Heroicons, Material Symbols o Font Awesome incluyen variantes del check listas para usar, normalmente como componentes SVG. En proyectos con muchos iconos, mantener una biblioteca coherente suele ser preferible a mezclar caracteres Unicode con imágenes. Una organización típica de los recursos sería:
src/
├── assets/
│ └── icons/
│ ├── check.svg
│ ├── check-circle.svg
│ └── cross.svg
├── components/
│ └── Icon.jsx
└── styles/
└── lists.css
Accesibilidad: lo que el lector de pantalla anuncia
Un check es un símbolo visual, y no todos los usuarios lo perciben igual. Las personas que utilizan lectores de pantalla oirán lo que el navegador expone en el árbol de accesibilidad, y aquí es donde muchos sitios fallan.
Cuando un carácter Unicode aparece en el contenido, los lectores de pantalla lo anuncian por su nombre, que puede ser “marca de verificación”, “check” o algo menos claro, según el lector y el idioma. En una lista de diez características, oír “marca de verificación” diez veces resulta repetitivo y no aporta información útil. Peor aún, en una tabla comparativa con checks y cruces, el significado real, incluido o no incluido, puede perderse por completo.
La solución es separar la decoración del significado:
- Si el símbolo es puramente decorativo y el texto ya transmite el significado, ocúltalo con
aria-hidden="true". - Si el símbolo transmite información que no está en el texto, añade un texto alternativo, visible o solo para lectores de pantalla.
Las listas de verificación accesibles combinan ambas ideas. En una tabla de planes, por ejemplo:
<table>
<caption>
Comparativa de planes
</caption>
<thead>
<tr>
<th scope="col">Característica</th>
<th scope="col">Básico</th>
<th scope="col">Pro</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Dominio propio</th>
<td>
<span aria-hidden="true">✗</span
><span class="sr-only">No incluido</span>
</td>
<td>
<span aria-hidden="true">✓</span
><span class="sr-only">Incluido</span>
</td>
</tr>
</tbody>
</table>
La clase sr-only oculta visualmente el texto sin retirarlo del árbol de accesibilidad:
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
Con los pseudoelementos de CSS hay que tener especial cuidado, porque el contenido generado con content sí suele exponerse a los lectores de pantalla. Las versiones actuales de CSS permiten indicar un texto alternativo para ese contenido, separado por una barra, y una cadena vacía lo marca como decorativo:
.caracteristica[data-incluida="true"]::before {
content: "\2713" / "";
}
En navegadores que todavía no admitan esta sintaxis, la declaración completa se ignoraría, por lo que conviene definir antes una regla de respaldo con el contenido sin texto alternativo.
Otro principio fundamental es no depender solo del color. Un check verde y una cruz roja son claros para muchas personas, pero no para quienes tienen dificultades para distinguir colores. Usar formas distintas, check frente a cruz, y acompañarlas de texto garantiza que el significado llegue a todos.
Por último, si lo que estás construyendo es una lista que el usuario puede marcar, no simules casillas con caracteres ☐ y ☑. Usa elementos <input type="checkbox"> reales con su <label>, que ofrecen navegación por teclado, estados anunciados por los lectores de pantalla y compatibilidad con formularios de forma nativa.
El check fuera del navegador: Markdown y terminal
El símbolo de verificación también aparece con frecuencia fuera de las páginas web, en documentación y herramientas de desarrollo.
En Markdown con la sintaxis de GitHub, las listas de tareas se escriben con corchetes, y la plataforma las convierte en casillas interactivas:
- [x] Configurar el repositorio
- [x] Añadir integración continua
- [ ] Publicar la primera versión
En la terminal, muchos scripts y herramientas de línea de comandos usan checks y cruces para mostrar resultados de forma legible. Siempre que el terminal use UTF-8, basta con imprimir el carácter:
pruebas = {"conexión a la base de datos": True, "migraciones": True, "caché": False}
for nombre, ok in pruebas.items():
simbolo = "\033[32m✓\033[0m" if ok else "\033[31m✗\033[0m"
print(f"{simbolo} {nombre}")
✓ conexión a la base de datos
✓ migraciones
✗ caché
Las secuencias \033[32m y \033[31m son códigos de escape ANSI que colorean el texto en verde y en rojo en la mayoría de los terminales modernos.
Errores comunes y cómo solucionarlos
Al trabajar con símbolos Unicode aparecen siempre los mismos problemas.
El primero es ver signos extraños en lugar del check, como ✓. Es el síntoma clásico de una discrepancia de codificación: el archivo está guardado en UTF-8, pero el navegador lo interpreta con otra codificación. La solución es declarar <meta charset="UTF-8"> al principio del head, comprobar que el servidor envía la cabecera Content-Type: text/html; charset=utf-8 y guardar los archivos en UTF-8 en el editor. Puedes revisar la cabecera con curl:
curl -sI https://tu-sitio.dev | grep -i content-type
content-type: text/html; charset=utf-8
El segundo es ver un recuadro vacío o un rectángulo con un código dentro, a veces llamado tofu. Significa que ninguna de las fuentes disponibles tiene un glifo para ese carácter. Ocurre sobre todo con caracteres poco comunes, como U+1F5F8. La solución es usar caracteres con amplio soporte, como U+2713 y U+2714, incluir en la pila de fuentes una alternativa que los contenga o recurrir a un SVG.
El tercero es intentar cambiar el color de un emoji. Si el check sigue apareciendo verde con fondo de color pese a aplicar color en CSS, estás usando un emoji. Sustitúyelo por la versión de texto o añade el selector de variación U+FE0E.
El cuarto es escribir mal la secuencia en CSS. En la propiedad content no funcionan las entidades HTML: content: "✓" mostrará literalmente el texto ✓. Hay que usar la secuencia de escape de CSS, "\2713".
El quinto es usar el índice o la longitud de cadena para manipular emojis en JavaScript, lo que puede cortar un carácter por la mitad. Itera con el operador de propagación o con Intl.Segmenter cuando trabajes con texto que pueda contener emojis.
Conclusiones
Mostrar un check en una página web parece trivial, pero hacerlo bien implica tomar algunas decisiones conscientes. Unicode ofrece una familia completa de marcas de verificación, desde el discreto U+2713 hasta el emoji U+2705, junto con sus opuestos para indicar lo que falta o ha fallado.
En HTML, esos caracteres pueden escribirse directamente con UTF-8 o mediante entidades numéricas y con nombre, como ✓, ✓ o ✓. En CSS, la propiedad content con secuencias de escape como "\2713" permite añadir checks decorativos a listas, tablas y componentes sin modificar el marcado, y ::marker simplifica las viñetas de las listas. Desde JavaScript, String.fromCodePoint y textContent permiten insertarlos de forma dinámica y segura.
Conviene distinguir entre glifos de texto, que heredan el color y la fuente, y emojis, que imponen su propio estilo según el sistema. Cuando el control visual es prioritario o se necesitan animaciones, los iconos SVG son la alternativa más robusta.
Por encima de todo, la accesibilidad debe guiar la implementación: ocultar los símbolos puramente decorativos, añadir texto alternativo cuando transmiten información, no depender solo del color y usar casillas de verificación reales cuando el usuario debe interactuar. Con estas pautas, un símbolo tan pequeño como el check comunicará exactamente lo que pretendes a todas las personas que visiten tu sitio.
