Skeleton loading com Claude Code: React, CLS e acessibilidade
Implemente skeleton loading com Claude Code: React, CLS, acessibilidade, erros comuns e verificações.
Skeleton loading é o padrão de UI que mostra a estrutura aproximada da tela enquanto os dados ainda estão carregando. Em termos simples, ele reserva o espaço da imagem, do título, do resumo e das ações antes de o conteúdo real chegar.
Um spinner só diz “algo está acontecendo”. Um skeleton também mostra “que tipo de conteúdo vai aparecer aqui”. Se uma imagem, um bloco de anúncio ou uma resposta de API entra em um espaço já reservado, a página tende a pular menos. Essa estabilidade visual se conecta às orientações do web.dev sobre Cumulative Layout Shift e Core Web Vitals.
Este guia mostra como pedir ao Claude Code uma implementação prática de skeleton loading, com exemplos React e CSS copiáveis, notas de acessibilidade, casos de falha e verificações leves. Para contexto relacionado, leia otimização de performance, lazy loading de imagens e o workflow de acessibilidade.
Divida o trabalho antes de editar
Um skeleton não é apenas um retângulo cinza. Uma funcionalidade real precisa de estados de carregamento, sucesso, vazio e erro, e esses estados devem compartilhar uma estrutura estável. Se você pedir ao Claude Code “faça um skeleton bonito”, ele pode entregar um shimmer elegante, mas esquecer erro, movimento reduzido ou mensagens para leitores de tela.
flowchart LR
P["Prompt para Claude Code"] --> S["skeleton com tamanho similar"]
S --> D["dados carregados"]
D --> E["estado vazio"]
D --> X["estado de erro"]
S --> A["aria-busy / status"]
S --> M["prefers-reduced-motion"]
S --> C["verificação de CLS"]
Comece com um prompt que defina escopo e verificação:
Read the existing card/list components before editing.
Implement skeleton loading only for the article cards list.
Keep the skeleton dimensions close to the loaded content.
Handle loading, empty, error, and success states.
Respect prefers-reduced-motion and avoid layout shift.
Add a small Playwright check if the project already uses Playwright.
Do not change unrelated styles, routing, or data fetching.
prefers-reduced-motion é uma condição CSS que identifica se o usuário pediu menos movimento no sistema ou no navegador. Shimmer forte pode incomodar algumas pessoas, então consulte a referência da MDN sobre prefers-reduced-motion e ofereça uma alternativa sem animação.
Casos de uso práticos
Skeleton loading funciona melhor quando o usuário consegue entender o que virá, mas os dados ainda não podem ser exibidos.
| Caso de uso | O que reservar | Atenção |
|---|---|---|
| Lista de cards de artigos | Miniatura, título em duas linhas, resumo, tag | Mantenha a altura da mídia fixa |
| Dashboards | Cards de KPI, quadros de gráfico, atividade recente | Não mostre números parciais que possam confundir |
| Grades de ecommerce | Imagem, nome, preço, avaliação | Evite preço ou estoque antigo durante atualização |
| Tabelas administrativas | Cabeçalho, linhas, área de ações | Se o número de linhas varia muito, revise paginação e filtros |
| Landing pages de consultoria | Provas, CTA, FAQ | CTA tardio pode empurrar o caminho de conversão para baixo |
Em um fluxo de consultoria da ClaudeCodeLab, o maior ganho veio de reservar a altura dos cards e do CTA antes da chegada dos dados. O erro foi criar um skeleton mais alto que o conteúdo final: durante o carregamento parecia calmo, mas depois a página subia. Skeleton não é decoração; é um contrato de layout.
Exemplo React copiável
Cole este exemplo em src/App.tsx em um projeto Vite + React + TypeScript. Ele simula latência com setTimeout e permite alternar entre sucesso, vazio e erro. Este é também o nível de detalhe de estado que vale passar ao Claude Code antes de mexer em um repositório real.
import { useEffect, useState } from "react";
import "./skeleton-demo.css";
type Article = {
id: number;
title: string;
description: string;
tag: string;
};
type LoadState = "loading" | "success" | "empty" | "error";
const demoArticles: Article[] = [
{
id: 1,
title: "Criar diffs de UI mais seguros com Claude Code",
description: "Leia os componentes existentes e melhore só a experiência de carregamento.",
tag: "UX",
},
{
id: 2,
title: "Reservar espaço de imagem sem aumentar CLS",
description: "Fixe alturas de mídia, título e resumo antes de os dados chegarem.",
tag: "Performance",
},
{
id: 3,
title: "Estados de carregamento acessíveis",
description: "Combine aria-busy, mensagens status e comportamento com menos movimento.",
tag: "A11y",
},
];
function SkeletonLine({ width = "100%" }: { width?: string }) {
return <span className="sk-line" style={{ width }} aria-hidden="true" />;
}
function ArticleCardSkeleton() {
return (
<article className="article-card is-skeleton" aria-hidden="true">
<div className="sk-media" />
<div className="article-card__body">
<SkeletonLine width="46%" />
<SkeletonLine />
<SkeletonLine width="86%" />
<SkeletonLine width="32%" />
</div>
</article>
);
}
function ArticleCard({ article }: { article: Article }) {
return (
<article className="article-card">
<div className="article-card__media">{article.tag}</div>
<div className="article-card__body">
<p className="article-card__tag">{article.tag}</p>
<h2>{article.title}</h2>
<p>{article.description}</p>
</div>
</article>
);
}
export default function App() {
const [state, setState] = useState<LoadState>("loading");
const [articles, setArticles] = useState<Article[]>([]);
useEffect(() => {
const timer = window.setTimeout(() => {
setArticles(demoArticles);
setState("success");
}, 1200);
return () => window.clearTimeout(timer);
}, []);
const reloadAs = (nextState: LoadState) => {
setState("loading");
setArticles([]);
window.setTimeout(() => {
setArticles(nextState === "success" ? demoArticles : []);
setState(nextState);
}, 700);
};
return (
<main className="demo-shell">
<div className="demo-toolbar" aria-label="Alterar estado visível">
<button onClick={() => reloadAs("success")}>Sucesso</button>
<button onClick={() => reloadAs("empty")}>Vazio</button>
<button onClick={() => reloadAs("error")}>Erro</button>
</div>
<section
aria-busy={state === "loading"}
aria-describedby="article-list-status"
className="article-grid"
>
<p id="article-list-status" className="sr-only" role="status">
{state === "loading" ? "Carregando lista de artigos" : "Lista de artigos carregada"}
</p>
{state === "loading" &&
Array.from({ length: 3 }).map((_, index) => (
<ArticleCardSkeleton key={index} />
))}
{state === "success" &&
articles.map((article) => (
<ArticleCard key={article.id} article={article} />
))}
{state === "empty" && (
<div className="state-panel">Ainda não há artigos para mostrar.</div>
)}
{state === "error" && (
<div className="state-panel" role="alert">
Não foi possível carregar a lista de artigos. Tente novamente mais tarde.
</div>
)}
</section>
</main>
);
}
O ponto de acessibilidade mais importante é não anunciar o skeleton inteiro. Um leitor de tela não precisa ouvir que existem três linhas cinzas. Neste exemplo os cards skeleton usamaria-hidden, enquanto uma única mensagem comrole="status" descreve o estado da lista. A referência útil é ARIA status role na MDN.
CSS para tamanho e movimento estáveis
Salve este CSS comosrc/skeleton-demo.css. O essencial é mantermin-height, altura da mídia e espaçamento interno parecidos entre carregamento e conteúdo final. O shimmer é discreto e para quando o usuário prefere menos movimento.
:root {
color: #18212f;
background: #f6f7f9;
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
button {
min-height: 40px;
border: 1px solid #b8c2d6;
border-radius: 8px;
background: #ffffff;
color: #18212f;
padding: 0 14px;
font-weight: 700;
}
.demo-shell {
width: min(1040px, calc(100% - 32px));
margin: 40px auto;
}
.demo-toolbar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 18px;
}
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
.article-card {
min-height: 316px;
overflow: hidden;
border: 1px solid #d7deea;
border-radius: 8px;
background: #ffffff;
}
.article-card__media,
.sk-media {
display: grid;
min-height: 148px;
place-items: center;
background: #dfe7f3;
color: #39506f;
font-weight: 800;
}
.article-card__body {
display: grid;
gap: 10px;
padding: 18px;
}
.article-card__tag {
color: #3b6b4f;
font-size: 0.875rem;
font-weight: 800;
}
.article-card h2 {
min-height: 56px;
margin: 0;
font-size: 1.16rem;
line-height: 1.45;
}
.article-card p {
margin: 0;
line-height: 1.7;
}
.sk-line,
.sk-media {
border-radius: 8px;
background: linear-gradient(90deg, #d9e0ea 25%, #edf1f7 37%, #d9e0ea 63%);
background-size: 240% 100%;
animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
.sk-line {
display: block;
height: 16px;
}
.state-panel {
min-height: 180px;
display: grid;
place-items: center;
border: 1px solid #d7deea;
border-radius: 8px;
background: #ffffff;
padding: 24px;
text-align: center;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}
@keyframes skeleton-shimmer {
from {
background-position: 120% 0;
}
to {
background-position: -120% 0;
}
}
@media (prefers-reduced-motion: reduce) {
.sk-line,
.sk-media {
animation: none;
background: #d9e0ea;
}
}
Esse CSS prioriza estabilidade de layout, não um brilho dramático. Se as linhas skeleton tiverem larguras muito aleatórias, o conteúdo final parece entrar em outro design. Na prática, é melhor reservar título de duas linhas, resumo de duas linhas e uma área de mídia previsível.
Verificação mínima com Playwright
Se o projeto já usa Playwright, adicione um teste focado. Ele não prova que o CLS real está perfeito, mas pega regressões óbvias antes do review.
import { expect, test } from "@playwright/test";
test("article skeleton keeps a stable card area", async ({ page }) => {
await page.goto("/");
await expect(page.getByText("Carregando lista de artigos")).toBeAttached();
await expect(page.locator(".is-skeleton")).toHaveCount(3);
const firstBox = await page.locator(".article-card").first().boundingBox();
expect(firstBox?.height).toBeGreaterThan(280);
await page.getByRole("button", { name: "Erro" }).click();
await expect(page.getByRole("alert")).toContainText("Não foi possível carregar");
});
CLS real ainda depende de imagens, anúncios, fontes, scripts de terceiros, rede e dispositivos. Use este teste como alarme inicial e combine depois com a orientação do web.dev e dados de produção.
Erros comuns
O primeiro erro é um skeleton que não combina com o conteúdo final. Ele parece limpo durante o carregamento, mas o card real estica ou encolhe. Reserve dimensões de imagem comwidth, height ouaspect-ratio, e também reserve espaço para anúncios e CTAs antes de renderizar.
O segundo erro é mostrar skeleton para toda requisição rápida. Se a requisição normalmente termina em 100 ms, o skeleton pode criar flicker. Em produção, use regras como “mostrar depois de 300 ms”, “apenas no primeiro carregamento” ou “manter dados antigos durante atualização”.
O terceiro erro é barulho demais para acessibilidade. Se cada card tiver seu própriorole="status", tecnologias assistivas podem repetir mensagens. Mantenha a mensagem viva no nível da lista e oculte formas puramente visuais.
O quarto erro é esquecer o caminho de falha. Se a API falha e o skeleton fica para sempre, o usuário acha que a página ainda está trabalhando. Desenhe erro, vazio e tentar novamente como estados separados.
O quinto erro é pedir ao Claude Code que decida prioridade de produto. Claude Code pode ler arquivos e gerar código, mas uma pessoa decide qual CTA precisa continuar visível, quanto espaço de anúncio reservar e que conteúdo é seguro mostrar primeiro.
Prompt de revisão para Claude Code
Depois de implementar, mude para modo revisão:
Review only the skeleton loading changes.
Check whether loaded content and skeleton content reserve similar space.
Check loading, success, empty, and error states.
Check reduced-motion behavior and ARIA announcements.
Point out any code that may increase CLS or create repeated screen reader messages.
Return findings with file names and exact lines.
Isso transforma Claude Code de implementador em crítico. Em sites de conteúdo, anúncios, posts relacionados, CTAs e imagens lazy afetam o mesmo fluxo visual. Use o guia de CSS e as estratégias de teste para separar checks visuais, leitores de tela e regressão.
Relação com monetização
Skeleton loading também protege caminhos de receita. Se um CTA de consultoria, card de produto, formulário de newsletter ou anúncio aparece tarde e empurra o artigo, o leitor perde o ponto de leitura ou clica no lugar errado. Isso reduz confiança e conversão.
Desenvolvedores individuais podem começar com o cheatsheet gratuito de Claude Code para padronizar revisão. Equipes que querem transformar skeleton loading, lazy images, Core Web Vitals e acessibilidade em um fluxo comum podem usar treinamento e consultoria Claude Code. Masa pode revisar o repositório existente e converter melhorias em prompts, componentes e checks práticos.
Resumo
Bom skeleton loading não é truque para esconder espera. Ele reserva espaço parecido com a tela final, reduz incerteza e evita movimentos visíveis. Com Claude Code, entregue ao mesmo tempo o componente alvo, os estados, dimensões, requisitos de acessibilidade e comandos de verificação.
Ao testar este fluxo, as mudanças mais úteis foram fixar primeiro a altura de mídia e título, anunciar carregamento no nível da lista e parar a animação para usuários com movimento reduzido. Quando foquei só no shimmer, estados vazio e erro ficaram para depois e o review demorou mais. Peça implementação e verificação juntas.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Escada de segurança de permissões no Claude Code
Amplie de read-only para edições limitadas, comandos de prova e deploy checks sem perder controle.
Claude Code Small PR Proof Pack: pequenas mudanças fáceis de revisar
Um pacote de prova para PRs do Claude Code: diff, checks, URL pública, CTA e rollback.
Gate de revisão antes do commit com Claude Code
Revisão antes do commit com Claude Code: diff, build, URL pública, Gumroad, consultoria, testes e arquivos fora do escopo.