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.
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:
<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.
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);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:
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);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:
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'));Três resultados diferentes em três linhas parecidas:
backgroundColorfuncionou, e o valor voltou convertido parargb().width = 320sumiu. 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-topseria 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.
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);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:
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);Variável CSS (--alguma-coisa) só 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:
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);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:
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));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:
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);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:
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);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 é:
const item = document.querySelector('[data-id="1"]');
item.classList.add('em promocao');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 JavaScript — createElement: 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.
Perguntas frequentes
Por que element.style.width volta vazio se o CSS define a largura?
É melhor mexer em classe ou em style?
Por que minha atribuição de estilo não fez efeito nenhum?
Dá para ler uma variável CSS pelo JavaScript?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- MDN — Element: classList — developer.mozilla.org
- MDN — Window: getComputedStyle() — developer.mozilla.org



