Programação Por Equipe DevBras

Capa do artigo: Linha do tempo vertical em HTML e CSS: histórico que se lê de relance

Linha do tempo vertical em HTML e CSS: histórico que se lê de relance

Monte uma timeline vertical com lista ordenada, data em texto e uma linha de CSS, sem biblioteca. Funciona no celular, no leitor de tela e com dados vindos da API.

O cliente abre o pedido 4821 e encontra uma tabela com cinco linhas, colunas "evento", "data" e "usuário", em ordem de inserção no banco. Ele só queria saber uma coisa: o pacote já saiu? A resposta curta: uma linha do tempo resolve quando cada marco tem data em texto, um título curto e no máximo uma frase, e quando a ordem é óbvia sem legenda. Em HTML, isso é uma lista ordenada (<ol>) com elementos <time>; a linha vertical é só um detalhe de CSS.

A linha do tempo não é enfeite nem galeria. Ela existe para responder "o que veio antes, o que veio depois e onde estamos agora". Tudo o que compete com essa resposta, como foto grande, ícone em cada marco e animação ao rolar, deixa a página mais bonita e mais lenta de ler.

Por que a tabela e o mural de cards falham

Uma tabela trata cada evento como uma linha igual às outras. A pessoa precisa ler a coluna de data e montar a sequência na cabeça. Um mural de cards em grade é pior: a ordem de leitura vira esquerda para direita, de cima para baixo, e ninguém sabe se o card da direita veio antes ou depois do de baixo.

A linha do tempo vertical funciona porque usa a direção natural da rolagem. O olho desce, a história anda. No celular, que é onde a maioria vai olhar o status de um pedido, não existe espaço para outra coisa. Linhas do tempo horizontais têm seu lugar, mas cobram um preço alto em tela estreita, como discute o post sobre linha do tempo horizontal.

Outro motivo é semântico. Leitor de tela anuncia uma lista ordenada como "lista, 5 itens" e permite pular de item em item. Se a data estiver só num desenho ou numa cor, essa pessoa perde justamente a informação central.

O que cada marco carrega

  • Data e hora em texto, no formato que a pessoa usa ("03/03/2026, 10:12"), dentro de <time datetime> com o valor em formato de máquina.
  • Título curto, que sozinho já conta o fato: "Pagamento aprovado", "Pedido enviado".
  • Uma frase opcional com o detalhe útil: o código de rastreio, a transportadora, o motivo de um atraso.

Foto, botão e ícone são extras. Se todos os marcos têm foto grande, a data some e a página vira galeria. A linha vertical só existe para ligar os pontos. Ela não é o conteúdo.

Como montar a timeline do pedido, passo a passo

  1. Escolha a ordem e escreva-a no título. Para status, o mais recente em cima, porque a pessoa quer o agora. Para história de empresa ou de carreira, o mais antigo em cima, porque a narrativa começa no início. Escolha uma e diga: "do mais recente para o mais antigo".
  2. Use uma lista ordenada. Cada marco é um <li> com título, data e frase.
  3. Marque o passo atual com aria-current="step". O CSS usa o mesmo atributo para destacar o ponto, então o visual e a acessibilidade nunca divergem.
  4. Desenhe a linha com borda e o ponto com pseudo-elemento. Nenhuma imagem, nenhuma biblioteca.
  5. Mantenha uma coluna em qualquer largura. Alternar marcos à esquerda e à direita no desktop parece elegante e quebra a ordem de leitura.

O HTML

<section aria-labelledby="historico-titulo">
  <h2 id="historico-titulo">Histórico do pedido 4821</h2>
  <p>Do mais recente para o mais antigo.</p>
  <ol class="timeline">
    <li aria-current="step">
      <h3>Pedido enviado</h3>
      <time datetime="2026-03-04T15:40-03:00">04/03/2026, 15:40</time>
      <p>Transportadora Rápida Sul, código de rastreio RS123456789BR.</p>
    </li>
    <li>
      <h3>Nota fiscal emitida</h3>
      <time datetime="2026-03-04T09:05-03:00">04/03/2026, 09:05</time>
    </li>
    <li>
      <h3>Pagamento aprovado</h3>
      <time datetime="2026-03-03T10:14-03:00">03/03/2026, 10:14</time>
    </li>
    <li>
      <h3>Pedido recebido</h3>
      <time datetime="2026-03-03T10:12-03:00">03/03/2026, 10:12</time>
    </li>
  </ol>
</section>

O CSS

A lista recebe um recuo à esquerda e uma borda, que é a linha. Cada item ganha um ponto posicionado exatamente sobre a borda. A conta do left é simples: o item começa 1,5rem depois da borda de 2px; o ponto tem 14px de largura; para centralizar, você volta 1,5rem, mais 1px (metade da borda), mais 7px (metade do ponto), ou seja, calc(-1.5rem - 8px).

.timeline {
  list-style: none;
  margin: 0;
  padding: 0 0 0 1.5rem;
  border-left: 2px solid #c9ced6;
}

.timeline li {
  position: relative;
  padding: 0 0 1.5rem 1rem;
}

.timeline li::before {
  content: "";
  position: absolute;
  box-sizing: border-box;
  left: calc(-1.5rem - 8px);
  top: 0.3rem;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #5b6472;
  background: #fff;
}

.timeline li[aria-current="step"]::before {
  border-color: #1f6f43;
  background: #1f6f43;
}

.timeline h3 {
  margin: 0;
  font-size: 1rem;
}

.timeline time {
  display: block;
  color: #4a5160;
  font-size: 0.875rem;
}

Confira o contraste do cinza da data contra o fundo. Texto secundário claro demais é o erro mais comum nesse tipo de componente, e ele some de vez no sol, na tela do celular.

Quando os marcos vêm da API

No sistema real, os eventos vêm do servidor, com data em UTC, e o script preenche uma <ol class="timeline"> vazia. Formate no navegador com o fuso da loja e use textContent, nunca innerHTML, para que um texto vindo do banco não vire HTML executável.

const formato = new Intl.DateTimeFormat('pt-BR', {
  dateStyle: 'short',
  timeStyle: 'short',
  timeZone: 'America/Sao_Paulo'
});

function montarTimeline(lista, eventos) {
  const ordenados = [...eventos].sort((a, b) => b.em.localeCompare(a.em));
  ordenados.forEach((evento, indice) => {
    const li = document.createElement('li');
    if (indice === 0) li.setAttribute('aria-current', 'step');

    const titulo = document.createElement('h3');
    titulo.textContent = evento.titulo;

    const quando = document.createElement('time');
    quando.dateTime = evento.em;
    quando.textContent = formato.format(new Date(evento.em));

    li.append(titulo, quando);
    if (evento.detalhe) {
      const p = document.createElement('p');
      p.textContent = evento.detalhe;
      li.append(p);
    }
    lista.append(li);
  });
}

montarTimeline(document.querySelector('.timeline'), [
  { titulo: 'Pedido recebido', em: '2026-03-03T13:12:00Z' },
  { titulo: 'Pagamento aprovado', em: '2026-03-03T13:14:00Z' },
  { titulo: 'Pedido enviado', em: '2026-03-04T18:40:00Z', detalhe: 'Código de rastreio RS123456789BR.' }
]);

A ordenação por localeCompare funciona porque as datas estão todas em ISO 8601 com o mesmo fuso (Z). Se o servidor misturar formatos, ordene por new Date(...).getTime(). Guardar e transmitir em UTC, e só converter na exibição, evita o marco que aparece "antes" do anterior no horário de verão; o post sobre horário em UTC explica o porquê.

Erros comuns

  • Data só no desenho ou na posição. Leitor de tela e quem imprime a página perdem a informação principal.
  • Alternar marcos entre esquerda e direita. A ordem de leitura vira zigue-zague, e no celular a alternância nem cabe.
  • Linha horizontal com arraste no celular. Os marcos fora da tela ficam invisíveis, e ninguém arrasta para descobrir que existem.
  • Foto e ícone em todo marco. A data perde destaque e a página vira galeria.
  • Ordem implícita. Sem "do mais recente para o mais antigo" escrito, metade das pessoas lê ao contrário.
  • Trazer uma biblioteca de timeline para quatro itens. Você ganha dependência e peso para algo que cabe em 40 linhas de CSS, como discute a segunda biblioteca de timeline.

Checklist

  • A timeline é um <ol>, com um <li> por marco.
  • Cada marco tem data em texto dentro de <time datetime>.
  • A ordem está escrita no título ou logo abaixo dele.
  • O passo atual tem aria-current="step" e destaque visual vindo do mesmo atributo.
  • Em tela estreita, tudo cabe numa coluna, sem rolagem horizontal.
  • Textos vindos da API entram com textContent.
  • Datas são guardadas em UTC e formatadas no fuso de quem lê.

Pegue o histórico que hoje é uma tabela no seu sistema e escolha os três eventos que o cliente mais pergunta no suporte. Eles cabem numa timeline com folga, e o mais recente fica em cima porque a pessoa quer o agora, ou embaixo porque a sua história começa no início?

Avalie este artigo

Referência: https://tympanus.net/Blueprints/VerticalTimeline/