Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

classList e style: mudar o CSS com JavaScript

Como aplicar estilo pelo JavaScript com classList e element.style, por que getComputedStyle existe e o motivo de uma largura sem unidade falhar calada.

Rodolfo Mori7 min de leitura

Existem dois caminhos para mudar a aparência de um elemento pelo JavaScript: alterar as classes com elemento.classList ou escrever uma declaração no atributo style com algo como elemento.style.backgroundColor = '#fff7ed'.

Pense numa loja que já tem uniformes prontos para cada situação. Adicionar a classe esgotado é vestir o uniforme correspondente: cor, opacidade e outros detalhes continuam definidos juntos no CSS. Escrever em element.style é fazer um ajuste sob medida naquele item específico. No navegador, classList liga e desliga seletores da folha de estilo; style cria uma declaração inline, com prioridade própria na cascata.

Por isso, prefira classList para estados conhecidos, como menu aberto, item selecionado ou produto esgotado. Reserve style para valores que só existem em tempo de execução — a largura calculada de uma barra de progresso ou a posição de um tooltip.

Esta é a página dos exemplos, com a folha de estilo que vale o resto da lição:

html
<style>
  :root { --cor-marca: #22c55e; }
  .item { padding: 8px 12px; border: 1px solid #dddddd; }
  .esgotado { opacity: 0.5; }
  .badge { color: var(--cor-marca); }
  #trilho { width: 400px; background: #eeeeee; }
  #barra-frete { height: 8px; background: #22c55e; }
</style>

<ul class="itens">
  <li class="item" data-id="1" style="color: rgb(30, 41, 59)"><span class="nome">Teclado mecânico</span></li>
  <li class="item" data-id="2"><span class="nome">Mouse sem fio</span></li>
  <li class="item esgotado" data-id="3"><span class="nome">Cadeira gamer</span></li>
</ul>
<p class="badge">Frete grátis acima de R$ 199</p>
<div id="trilho"><div id="barra-frete"></div></div>

element.style só enxerga o atributo inline

Esta é a confusão número um do assunto. elemento.style não é “o estilo do elemento”: é a leitura e a escrita do atributo style="" daquela tag. Regra que veio da folha de estilo não aparece ali.

js
const item = document.querySelector('[data-id="1"]');

console.log('style.color  :', JSON.stringify(item.style.color));
console.log('style.padding:', JSON.stringify(item.style.padding));
console.log('style.border :', JSON.stringify(item.style.border));
console.log('quantas propriedades inline:', item.style.length);
style.color : "rgb(30, 41, 59)" style.padding: "" style.border : "" quantas propriedades inline: 1

O color apareceu porque está escrito no atributo style da tag. O padding e o border estão em branco, apesar de o elemento visivelmente ter os dois — eles vieram da regra .item na folha de estilo, que elemento.style não consulta.

style.length fecha o argumento: uma única propriedade, a inline.

getComputedStyle: o valor que realmente valeu

Para saber o que está valendo depois da cascata, da herança e dos valores padrão, existe getComputedStyle. Ele devolve um objeto somente leitura com todas as propriedades resolvidas:

js
const item = document.querySelector('[data-id="1"]');
const calculado = getComputedStyle(item);

console.log('padding    :', calculado.padding);
console.log('borderStyle:', calculado.borderStyle);
console.log('color      :', calculado.color);
console.log('display    :', calculado.display);

const esgotado = getComputedStyle(document.querySelector('.esgotado'));
console.log('opacity    :', esgotado.opacity);
padding : 8px 12px borderStyle: solid color : rgb(30, 41, 59) display : list-item opacity : 0.5

Três coisas para guardar. Primeiro: o display: list-item ninguém escreveu — é o padrão do <li>, e o valor calculado inclui os padrões do navegador. Segundo: as cores voltam sempre em rgb(), mesmo que você tenha escrito #22c55e na folha; comparar com a string do seu CSS não vai funcionar. Terceiro: o objeto é somente leitura. Atribuir nele não faz nada.

camelCase, e a unidade não é opcional

Escrever em elemento.style tem duas regras que não perdoam. Nome de propriedade com hífen vira camelCase, e todo valor de medida precisa de unidade:

js
const item = document.querySelector('[data-id="2"]');

item.style.backgroundColor = '#fff7ed';
item.style.width = 320;
item.style['margin-top'] = '12px';

console.log('backgroundColor:', item.style.backgroundColor);
console.log('width          :', JSON.stringify(item.style.width));
console.log('marginTop      :', item.style.marginTop);
console.log(item.getAttribute('style'));
backgroundColor: rgb(255, 247, 237) width : "" marginTop : 12px background-color: rgb(255, 247, 237); margin-top: 12px;

Três resultados diferentes em três linhas parecidas:

  • backgroundColor funcionou, e o valor voltou convertido para rgb().
  • width = 320 sumiu. O número virou a string "320", que não é um comprimento CSS válido, e o navegador descartou. Sem erro, sem aviso, sem nada. É o pior tipo de falha, porque não deixa rastro.
  • item.style['margin-top'] funcionou. A notação de colchetes aceita o nome original com hífen, e é uma saída legítima quando o nome vem de variável. Com ponto, item.style.margin-top seria uma subtração.

A barra de progresso que não anda

Vale ver o caso concreto, porque é onde esse erro aparece na vida real: uma barra que mostra quanto falta para o frete grátis.

js
const barra = document.getElementById('barra-frete');
const progresso = 62;

barra.style.width = progresso;
console.log('style.width :', JSON.stringify(barra.style.width));
console.log('largura real:', getComputedStyle(barra).width);

barra.style.width = progresso + '%';
console.log('style.width :', JSON.stringify(barra.style.width));
console.log('largura real:', getComputedStyle(barra).width);
style.width : "" largura real: 400px style.width : "62%" largura real: 248px

Na primeira tentativa, a barra ficou com 400px — a largura inteira do trilho, porque a atribuição não pegou e um <div> ocupa o pai todo. Visualmente parece “100% carregado”, o que é um bug bem convincente: a tela mostra algo plausível, só que errado.

Com o % concatenado, 62% de 400px dão os 248px que o navegador calculou.

setProperty: hífen, !important e variável

Existe uma API formal para escrever estilo, e ela resolve o que a notação de ponto não alcança — nome com hífen, prioridade e propriedades customizadas:

js
const item = document.querySelector('[data-id="1"]');

item.style.setProperty('padding', '4px', 'important');
item.style.setProperty('--altura-linha', '1.6');

console.log(item.getAttribute('style'));
console.log('prioridade:', JSON.stringify(item.style.getPropertyPriority('padding')));
console.log('padding real:', getComputedStyle(item).padding);
color: rgb(30, 41, 59); padding: 4px !important; --altura-linha: 1.6; prioridade: "important" padding real: 4px

Variável CSS (--alguma-coisa) entra por setProperty — o hífen duplo não tem forma camelCase. O terceiro argumento é a prioridade, e é a única maneira de escrever !important pelo JavaScript.

Use o important com parcimônia: ele vence a folha de estilo inteira e deixa o próximo desenvolvedor sem saída, porque não há como sobrescrever depois.

classList: a interface que você vai usar mais

classList é um objeto com métodos para mexer nas classes sem manipular string:

js
const item = document.querySelector('[data-id="3"]');

console.log('classes iniciais:', item.className);

item.classList.add('destaque', 'promo');
console.log('depois do add   :', [...item.classList].join(' '));

item.classList.remove('esgotado');
console.log('contém esgotado?', item.classList.contains('esgotado'));
console.log('contém promo?   ', item.classList.contains('promo'));

console.log('toggle promo ->', item.classList.toggle('promo'));
console.log('toggle promo ->', item.classList.toggle('promo'));

item.classList.replace('destaque', 'destaque-forte');
console.log('classes finais  :', item.className);
classes iniciais: item esgotado depois do add : item esgotado destaque promo contém esgotado? false contém promo? true toggle promo -> false toggle promo -> true classes finais : item destaque-forte promo

Detalhes que a saída mostra: add aceita vários nomes de uma vez; toggle devolve true quando a classe ficou e false quando saiu, o que dá para usar direto num if; e add de uma classe que já existe não duplica nada. remove de uma que não existe também não reclama.

toggle com segundo argumento

O toggle aceita um segundo parâmetro booleano que decide, em vez de alternar: true garante que a classe fique, false garante que ela saia. Isso substitui o if/else que todo mundo escreve:

js
const item = document.querySelector('[data-id="1"]');

function pintarEstoque(elemento, quantidade) {
  elemento.classList.toggle('esgotado', quantidade === 0);
  return elemento.className;
}

console.log(pintarEstoque(item, 0));
console.log(pintarEstoque(item, 5));
console.log(pintarEstoque(item, 0));
item esgotado item item esgotado

Uma linha, idempotente: chamar duas vezes com o mesmo valor dá o mesmo resultado. É o padrão certo para sincronizar classe com estado — e o if que você escreveria no lugar tem duas chances a mais de esquecer um caso.

className = atropela o que estava lá

A propriedade antiga, className, é a string inteira do atributo class. Atribuir nela troca tudo:

js
const item = document.querySelector('[data-id="3"]');

console.log('antes :', item.className);
console.log('padding antes :', getComputedStyle(item).padding);

item.className = 'destaque';

console.log('depois:', item.className);
console.log('padding depois:', getComputedStyle(item).padding);
antes : item esgotado padding antes : 8px 12px depois: destaque padding depois: 0px

Você queria acrescentar destaque e apagou item junto — o padding foi embora com ela. className continua útil para ler a string toda, ou para definir o conjunto de classes de um elemento que você acabou de criar. Para modificar o que já está na página, classList.

Variáveis CSS: um setProperty repinta a página inteira

Este é o truque que faz o botão de tema escuro funcionar sem nenhum laço. Escreva a variável no :root e todo elemento que a usa muda junto:

js
const raiz = document.documentElement;
const badge = document.querySelector('.badge');

console.log('--cor-marca:', getComputedStyle(raiz).getPropertyValue('--cor-marca'));
console.log('cor do badge:', getComputedStyle(badge).color);

raiz.style.setProperty('--cor-marca', '#a855f7');

console.log('--cor-marca:', getComputedStyle(raiz).getPropertyValue('--cor-marca'));
console.log('cor do badge:', getComputedStyle(badge).color);
--cor-marca: #22c55e cor do badge: rgb(34, 197, 94) --cor-marca: #a855f7 cor do badge: rgb(168, 85, 247)

Ninguém tocou no .badge. Ele usa color: var(--cor-marca), e a variável mudou no ancestral — a cascata se encarrega do resto. Numa página real, uma linha dessas troca a cor de dezenas de elementos ao mesmo tempo.

Repare também que a variável volta como você escreveu (#a855f7), enquanto a cor resolvida do elemento volta em rgb(). Variável CSS é texto até ser usada.

O erro que dá exceção: classe com espaço

Quase todos os erros desta lição são silenciosos. Este não é:

js
const item = document.querySelector('[data-id="1"]');

item.classList.add('em promocao');
Uncaught InvalidCharacterError: Failed to execute 'add' on 'DOMTokenList': The token provided ('em promocao') contains HTML space characters, which are not valid in tokens. at promocao.js:3:16

Classe é um token, e token não tem espaço. Para adicionar duas classes, passe duas: classList.add('em', 'promocao') — ou, mais provavelmente, o que você queria era uma classe só chamada em-promocao.

Vale saber que classList.add('') também lança, com SyntaxError. É o caso que aparece quando o nome da classe vem de uma variável que veio vazia.

Classe ou style: como decidir

situação use
item esgotado, linha selecionada, menu aberto classList.toggle
tema claro ou escuro setProperty numa variável no :root
largura de barra de progresso, posição de tooltip element.style com unidade
ler o que está valendo agora getComputedStyle
animação com transição classe — o CSS faz a transição sozinho

O critério é simples: se o valor pode ser escrito antes, na folha de estilo, ele mora no CSS e o JavaScript só liga a chave. Se o valor só existe em tempo de execução — porque depende de uma conta, de uma medida ou de um dado do servidor —, aí é style.

Um último detalhe que economiza dor de cabeça: quando você troca uma classe e quer animação, deixe a transition no CSS e mexa só na classe. Tentar animar por JavaScript mudando style a cada quadro custa caro e fica pior.

Agora teste a decisão: crie uma classe .selecionado com fundo azul, alterne-a com classList.toggle e confirme no DevTools que o atributo class muda. Depois defina element.style.width = '50%' e confirme que surgiu um atributo style. Apague a declaração inline e observe a largura voltar ao valor da folha. O exercício mostra onde cada tipo de mudança fica registrado.

Falta uma peça para você montar interface de verdade: criar elementos do zero, em vez de mexer nos que já estão na página. É a próxima lição da trilha de JavaScriptcreateElement: criar e remover elementos. Se quiser rever como chegar até o elemento antes de estilizá-lo, volte para selecionar elementos; o guia de JavaScript tem a ordem completa.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • dom
  • classlist
  • style
  • getcomputedstyle
  • variaveis css

Perguntas frequentes

Por que element.style.width volta vazio se o CSS define a largura?
Porque element.style é a leitura do atributo style do próprio elemento, e não o resultado da cascata. Se a regra veio da folha de estilo, esse atributo está vazio. O valor que vigora sai do getComputedStyle.
É melhor mexer em classe ou em style?
Classe, quase sempre. O visual continua morando no CSS, o JavaScript só liga e desliga estado, e o designer consegue mudar a aparência sem tocar no código. Reserve o style para valor calculado em tempo real.
Por que minha atribuição de estilo não fez efeito nenhum?
As duas causas comuns são unidade faltando e nome com hífen. Valor de medida sem px ou por cento é inválido e o navegador descarta em silêncio; background-color com hífen precisa virar backgroundColor.
Dá para ler uma variável CSS pelo JavaScript?
Dá, com getComputedStyle(elemento).getPropertyValue('--nome'). O valor vem como string, exatamente como escrito na folha, e costuma vir com um espaço na frente — vale um trim antes de comparar.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Chromium 151.0.7922.34, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Element: classList — developer.mozilla.org
  2. MDN — Window: getComputedStyle() — developer.mozilla.org

Continue por aqui