WhatsApp

  

Progress steps

Guía práctica para construir un visual de pasos de progreso con HTML, CSS y JavaScript — personalizable y accesible

Los progress steps (o pasos de progreso), son un patrón de interfaz muy usado para guiar al usuario a través de procesos largos divididos en etapas, ofreciendo a la vez orientación, retroalimentación y control

En este blog encontrarás cómo realizar este visual utilizando HTML, CSS y JavaScript, con la estructura en archivos individuales y una versión embebible para que puedas adaptarlo a tus necesidades: colores, número de pasos, etiquetas, interacción y accesibilidad.

1) Index.hmtl 

Es el esqueleto del componente. Define la estructura y el contenido visibles del “progress steps”: el contenedor de la barra, los pasos (círculos) y los botones para avanzar/retroceder. Su código es el siguiente:

<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Progress Steps</title>
  <link rel="stylesheet" href="styles.css" />
</head>
<body>
  <main class="wrap">
    <h1>Progreso</h1>
    <!-- Contenedor de progreso -->
    <div class="progress-container" id="progressContainer" aria-label="Pasos de progreso">
      <!-- La barra que “crece”; su width la cambia el JS -->
      <div class="progress" id="progress"></div>
      <!-- Duplica o elimina círculos para cambiar el total de pasos -->
      <div class="circle active">1</div>
      <div class="circle">2</div>
      <div class="circle">3</div>
      <div class="circle">4</div>
    </div>
    <!-- Acciones -->
    <div class="actions">
      <button id="prev" type="button" disabled>Anterior</button>
      <button id="next" type="button">Siguiente</button>
    </div>
  </main>
  <!-- Carga el JS al final para evitar problemas de “no avanza” -->
  <script src="script.js"></script>
</body>
</html>


F​uncionamiento

  • Cabecera (<head>): configura el documento (<!doctype html>, idioma, charset, viewport) y vincula los estilos con <link rel="stylesheet" href="styles.css">.

  • Contenedor principal (<main class="wrap">): agrupador visual y semántico del contenido; dentro va el título <h1>Progreso</h1>.
  • Módulo de progreso 
<div class="progress-container" id="progressContainer" aria-label="Pasos de progreso">
  <div class="progress" id="progress"></div>
  <div class="circle active">1</div>
  <div class="circle">2</div>
  <div class="circle">3</div>
  <div class="circle">4</div>
</div>

  • progress-container: agrupa el visual y añade aria-label para accesibilidad.

  • #progress: la barra cuyo width actualizará el JS.

  • .circle: cada paso; el primero inicia con .active.
  • Acciones(botones): Botones para retroceder/avanzar; “Anterior” inicia deshabilitado.
<div class="actions">
  <button id="prev" type="button" disabled>Anterior</button>
  <button id="next" type="button">Siguiente</button>
</div>




2) Script.js

Se encarga del comportamiento del componente. Mantiene el estado del paso actual (current), actualiza la interfaz (activa/desactiva círculos, ajusta el ancho de la barra de progreso) y controla la navegación con los botones Anterior/Siguiente. Su código es el siguiente:

// Selecciones del DOM
const progress = document.getElementById("progress");
const circles  = Array.from(document.querySelectorAll(".circle"));
const prevBtn  = document.getElementById("prev");
const nextBtn  = document.getElementById("next");
// Estado actual (arranca en el paso 1)
let current = 1;
// Actualiza UI: estados activos, ancho de la barra, botones y ARIA
function update() {
  // Activa círculos hasta el índice 'current'
  circles.forEach((c, i) => {
    const isActive = i < current;
    c.classList.toggle("active", isActive);
    // Accesibilidad (opcional): marca el step actual
    // Solo el círculo del paso actual lleva aria-current="step"
    if (i === current - 1) {
      c.setAttribute("aria-current", "step");
    } else {
      c.removeAttribute("aria-current");
    }
  });
  // Porcentaje de barra: (activos - 1) / (total - 1)
  const percent = (current - 1) / (circles.length - 1) * 100;
  progress.style.width = `${percent}%`;
  // Estado de botones
  prevBtn.disabled = current === 1;
  nextBtn.disabled = current === circles.length;
}
// Handlers de navegación
nextBtn.addEventListener("click", () => {
  current = Math.min(current + 1, circles.length);
  update();
});
prevBtn.addEventListener("click", () => {
  current = Math.max(current - 1, 1);
  update();
});
// —— Opcional: permitir clic en los pasos para saltar ——
// Descomenta si quieres que al hacer clic en un círculo salte a ese step.
/*
circles.forEach((c, i) => {
  c.style.cursor = "pointer";
  c.addEventListener("click", () => {
    current = i + 1;
    update();
  });
});
*/
// Inicializa vista
update();


F​uncionamiento​

script.js selecciona los elementos del DOM (#progress, todos los .circle, y los botones #prev/#next) y mantiene un estado llamado current que indica el paso activo. La función update() es el corazón:

  1. marca como active los círculos con índice menor a current,

  2. calcula el ancho de la barra con la fórmula ((current - 1)/(totalPasos - 1))*100 y lo asigna a style.width,

  3. habilita/deshabilita los botones según si estás en el primer o último paso.

Luego, añade event listeners a los botones: “Siguiente” aumenta current (hasta el total), “Anterior” lo disminuye (hasta 1) y siempre llama a update() para refrescar la UI. Por último, ejecuta update() una vez al cargar para mostrar el estado inicial.



3) style.css

Define la presentación visual del componente. Controla la paleta de colores, tipografías, tamaños, la línea base del progreso, la barra que crece y los estados de cada círculo (normal/activo). También estiliza los botones y asegura que la barra no “tape” los números usando capas (z-index) y isolation:isolate. Su código es el siguiente:

/* ==== Tema (ajusta colores/tamaños aquí) ===================== */
:root{
  --accent: #4f46e5;   /* Color principal: barra y activo */
  --bg:     #0f1115;   /* Fondo general (opcional) */
  --card:   #141821;   /* Fondo del contenedor .wrap */
  --text:   #f5f7fa;   /* Texto principal */
  --muted:  #9aa4b2;   /* Texto suave */
  --line:   #2a3140;   /* Línea base del progreso */
  --radius: 18px;
}
/* ==== Layout base opcional (para demo) ======================= */
html, body { height: 100%; }
body{
  margin: 0;
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  color: var(--text);
  background: var(--bg);
  display: grid;
  place-items: center;
}
.wrap{
  width: min(720px, 94vw);
  background: var(--card);
  border-radius: var(--radius);
  padding: 28px;
  box-shadow: 0 10px 40px rgba(0,0,0,.35);
}
h1{ margin: 0 0 18px; font-weight: 700; letter-spacing: .3px; }
/* ==== Módulo de progreso ==================================== */
.progress-container{
  position: relative;
  display: flex;
  justify-content: space-between;
  margin: 26px 0 30px;
  isolation: isolate;              /* evita que la barra tape los números */
}
/* Línea base (pista) */
.progress-container::before{
  content: "";
  position: absolute;
  height: 4px;
  width: 100%;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
  background: var(--line);
  border-radius: 999px;
  z-index: 0;                      /* al fondo */
}
/* Barra que crece */
.progress{
  position: absolute;
  height: 4px;
  width: 0%;                       /* el JS la actualiza */
  top: 50%;
  left: 0;
  transform: translateY(-50%);
  background: var(--accent);       /* cambia el color del progreso aquí */
  transition: width .35s ease;
  border-radius: 999px;
  z-index: 1;                      /* sobre la línea base */
}
/* Pasos (círculos) */
.circle{
  position: relative;
  z-index: 2;                      /* por encima de la barra */
  background: #1b2130;
  color: var(--muted);
  border: 3px solid var(--line);
  height: 42px;
  width: 42px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 700;
  transition: border-color .3s, color .3s, background .3s, box-shadow .25s;
}
/* Estado activo/completado */
.circle.active{
  border-color: var(--accent);
  color: #fff;
  background: #242b3e;
  box-shadow: 0 0 0 4px rgba(79,70,229,.12) inset; /* halo sutil */
}
/* ==== Acciones ============================================== */
.actions{
  display: flex;
  gap: 12px;
  justify-content: center;
}
button{ font: inherit; }
#prev, #next{
  background: var(--accent);
  color: #fff;
  border: 0;
  padding: 10px 18px;
  border-radius: 999px;
  font-weight: 600;
  cursor: pointer;
  transition: transform .06s ease, filter .2s ease;
}
#prev:hover:not(:disabled), #next:hover:not(:disabled){ filter: brightness(1.06); }
#prev:disabled, #next:disabled{ opacity: .45; cursor: not-allowed; }
#prev:not(:disabled):active, #next:not(:disabled):active{ transform: translateY(1px); }
/* ==== Responsivo (muchos pasos en móvil) ==================== */
@media (max-width: 480px){
  .circle{
    width: 36px;
    height: 36px;
    font-size: .95rem;
  }
}


F​uncionamiento

  • Tema: variables en :root (--accent, --line, --radius, etc.) para color y tamaños.

  • Layout demo: body y .wrap solo dan fondo/tarjeta; no afectan la lógica.

  • Contenedor: .progress-container con flex reparte los pasos; isolation:isolate evita que la barra tape los números.

  • Línea base: ::before dibuja la pista (detrás) con z-index:0.

  • Barra de progreso: .progress es la franja que crece; el JS cambia width; aquí controlas color y transition.

  • Pasos: .circle define tamaño/borde; .circle.active cambia a color de acento y resalta.

  • Botones: estilos visuales para #prev/#next (hover/disabled).

  • Responsivo: media query reduce tamaño de .circle en móvil.

Alternativa código compacto

Si prefieres probarlo en un solo archivo, aquí tienes el código compacto: es exactamente la misma lógica y estilos de los tres archivos (HTML, CSS y JS), solo reorganizado dentro de un único documento para copiar y ejecutar al instante:


<!doctype html>
<html lang="es">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Progress Steps — Compact</title>
<style>
*{box-sizing:border-box}
:root{
  /* Paleta de colores */
  --accent:#4f46e5;   /* color de barra y pasos activos */
  --bg:#0f1115;       /* fondo de página (demo) */
  --card:#141821;     /* tarjeta contenedora */
  --text:#f5f7fa;     /* texto */
  --muted:#9aa4b2;    /* texto suave */
  --line:#2a3140;     /* línea base */
  --radius:18px;
}
html,body{height:100%}
body{
  margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
  background:var(--bg);color:var(--text);display:grid;place-items:center
}
.wrap{
  width:min(720px,94vw);background:var(--card);border-radius:var(--radius);
  padding:28px;box-shadow:0 10px 40px rgba(0,0,0,.35)
}
h1{margin:0 0 18px}
.progress-container{
  position:relative;display:flex;justify-content:space-between;
  margin:26px 0 30px;isolation:isolate /* evita que la barra tape los números */
}
.progress-container::before{
  content:"";position:absolute;inset:0 0 0 0;height:4px;top:50%;transform:translateY(-50%);
  background:var(--line);border-radius:999px;z-index:0
}
.progress{
  position:absolute;height:4px;width:0%;top:50%;left:0;transform:translateY(-50%);
  background:var(--accent);transition:width .35s;border-radius:999px;z-index:1
}
.circle{
  position:relative;z-index:2;background:#1b2130;color:var(--muted);
  border:3px solid var(--line);height:42px;width:42px;border-radius:50%;
  display:grid;place-items:center;font-weight:700;
  transition:border-color .3s,color .3s,background .3s,box-shadow .25s
}
.circle.active{
  border-color:var(--accent);color:#fff;background:#242b3e;
  box-shadow:0 0 0 4px rgba(79,70,229,.12) inset
}
.actions{display:flex;gap:12px;justify-content:center}
.btn{
  background:var(--accent);color:#fff;border:0;padding:10px 18px;border-radius:999px;
  font-weight:600;cursor:pointer;transition:transform .06s,filter .2s
}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn:hover:not(:disabled){filter:brightness(1.06)}
.btn:active:not(:disabled){transform:translateY(1px)}
@media (max-width:480px){
  .circle{width:36px;height:36px;font-size:.95rem}
}
</style>
<main class="wrap">
  <h1>Progreso</h1>
  <div class="progress-container" aria-label="Pasos de progreso">
    <div class="progress" id="progress"></div>
    <!-- Para agregar pasos, duplica más círculos abajo -->
    <div class="circle active">1</div>
    <div class="circle">2</div>
    <div class="circle">3</div>
    <div class="circle">4</div>
  </div>
  <div class="actions">
    <button id="prev" class="btn" type="button" disabled>Anterior</button>
    <button id="next" class="btn" type="button">Siguiente</button>
  </div>
</main>
<script>
const progress=document.getElementById("progress");
const circles=[...document.querySelectorAll(".circle")];
const prev=document.getElementById("prev");
const next=document.getElementById("next");
let current=1;
function update(){
  circles.forEach((c,i)=>{
    const active=i<current;
    c.classList.toggle("active",active);
    if(i===current-1) c.setAttribute("aria-current","step");
    else c.removeAttribute("aria-current");
  });
  progress.style.width=((current-1)/(circles.length-1)*100)+"%";
  prev.disabled=current===1;
  next.disabled=current===circles.length;
}
next.addEventListener("click",()=>{ current=Math.min(current+1,circles.length); update(); });
prev.addEventListener("click",()=>{ current=Math.max(current-1,1); update(); });
update();
</script>
</html>



Resultado final


¡Y listo! Con esto en mano podrás implementar el visual de pasos de progreso donde lo necesites: ajustar colores, sumar etapas, añadir etiquetas o integrarlo en cualquier flujo (registro, checkout, onboarding). Solo adapta el HTML, personaliza el CSS y conecta el JS… ¡y a producir!

 



Progress steps
Nicol Espitia 21 octubre, 2025
Compartir
Iniciar sesión dejar un comentario

  
Sound Board
crea un tablero de sonidos con HTML, CSS y JS