Skip to content
Versão preliminar. Cores, componentes e layout ainda vão mudar: falta receber o padrão de interface do TJES (template-basico-vue) e o manual de identidade visual.

BaseSkeleton ​

Ocupa o lugar do conteúdo enquanto ele carrega, com a forma do que vai aparecer. Evita que a tela pule quando os dados chegam.

Uso ​

  • Use no primeiro carregamento de uma área: lista, tabela, cartão, painel.
  • Coloque o esqueleto sobre uma superfície --tj-layer-01 (cartão ou painel). Direto no fundo da página (--tj-background), a forma fica quase invisível.
  • Desenhe a forma do conteúdo real: linhas para texto, retângulo para imagem ou gráfico, círculo para avatar.
  • Para uma ação do usuário, como salvar, use o loading do botão, não o esqueleto.
  • O brilho roda três vezes (4,2 s) e para; a forma continua parada no lugar. Se a espera passar de 5 s, troque o esqueleto por um texto visível na tela, como "Os contratos estão demorando para carregar", não só pelo status oculto.
  • Quando os dados chegam, troque o esqueleto pelo conteúdo de uma vez, sem animação de saída.

Variantes ​

VarianteQuandoMedidas
textTítulos e parágrafoslines linhas, de 1 a 20; a última tem 60% da largura
rectImagens, gráficos, campos, cartõeswidth e height
circleAvatares e ícones redondoswidth é o diâmetro; continua redondo numa linha flex

Tamanhos ​

O size acompanha o texto que a linha substitui: sm para 13 px (o texto small), md para 14 px, lg para 16 px. Um bloco de N linhas tem a altura de N linhas do texto real (18, 20 ou 24 px cada), então nada pula quando o conteúdo chega. No rect e no circle, ele dá a medida padrão (32, 40 ou 48 px) quando height ou width não são informados.

Estilo ​

PropriedadeToken
Fundo--tj-layer-accent-01, sobre --tj-layer-01
Brilho--tj-layer-hover-01; no tema escuro, --tj-layer-accent-02
Raio--tj-radius-sm; circle é redondo
Altura da linha--tj-text-sm/md/lg (13, 14, 16 px)
Espaço entre linhas e nas bordas do bloco--tj-leading-* menos --tj-text-*; metade disso acima da primeira e abaixo da última linha
Medida padrão de rect e circle--tj-control-sm/md/lg (32, 40, 48 px)
Animação1,4 s por ciclo, três ciclos, como no protótipo; --tj-easing-standard

No tema escuro os tokens de camada trocam sozinhos, e o brilho usa --tj-layer-accent-02 para continuar mais claro que o fundo, como no tema claro. Com prefers-reduced-motion, o brilho não roda e a forma fica parada. No modo de alto contraste do sistema, cada forma ganha contorno.

Código ​

vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'

const loading = ref(true)
const statusText = ref('')
const contracts = ref<Contract[]>([])

onMounted(async () => {
  // The status region is already on the page; only its text changes.
  statusText.value = 'Carregando contratos'
  try {
    contracts.value = await fetchContracts()
    statusText.value = 'Contratos carregados'
  } catch {
    statusText.value = 'Não deu para carregar os contratos'
  } finally {
    // Success or failure, the area stops being busy.
    loading.value = false
  }
})
</script>

<template>
  <p class="visually-hidden" role="status">{{ statusText }}</p>
  <section :aria-busy="loading || undefined" aria-labelledby="contracts-title">
    <h2 id="contracts-title">Contratos</h2>
    <template v-if="loading">
      <BaseSkeleton :lines="3" />
      <BaseSkeleton variant="rect" height="160px" />
    </template>
    <ContractList v-else :contracts="contracts" />
  </section>
</template>
PropriedadeTipoPadrãoDescrição
variant'text' | 'rect' | 'circle''text'Forma do conteúdo que vai chegar
size'sm' | 'md' | 'lg''md'Altura da linha no text; medida padrão no rect e no circle
linesnumber1Número de linhas do text, de 1 a 20; valores fora disso são ajustados ao limite
widthstring100%; no circle, o sizeQualquer medida CSS, como '60%' ou '120px'
heightstringo sizeAltura do rect, em qualquer medida CSS; ignorada no text e no circle

Sem eventos nem slots.

Acessibilidade ​

  • As formas são decorativas: recebem aria-hidden="true" e não chegam ao leitor de tela.
  • Quem avisa que algo está carregando é o contêiner da área, não o esqueleto:
    • marque a área com aria-busy="true" enquanto carrega e tire o atributo quando o conteúdo chegar (:aria-busy="loading || undefined"; com false, o Vue deixaria aria-busy="false" no HTML);
    • tire o aria-busy também quando a carga falhar (no finally), senão a mensagem de erro fica presa dentro de uma área ocupada e o brilho não some;
    • coloque o texto de status, com class="visually-hidden" e role="status", fora da área com aria-busy: NVDA e JAWS seguram os avisos de dentro de uma área ocupada até ela deixar de estar ocupada;
    • deixe a região de status sempre na página e mude só o texto dela ("Carregando contratos", depois "Contratos carregados"). Uma região que já entra na página com texto costuma não ser anunciada.
  • O esqueleto não recebe foco e não tem interação de teclado.
  • A cor nunca é o único sinal: o texto de status diz que a área está carregando.
  • O brilho respeita prefers-reduced-motion e, mesmo sem essa preferência, para sozinho antes de 5 s (WCAG 2.2.2).
  • Testado com axe nas três variantes em cada tamanho, sozinhas e dentro de uma área com aria-busy, com o status fora dela e o texto dele definido depois da montagem. Um teste confere que a regra de prefers-reduced-motion desliga a animação.
  • O jsdom não calcula layout, então as medidas foram conferidas no Chromium: 3 linhas md dão 60 px, iguais a 3 linhas de texto de 14/20 px (sm: 54 px; lg: 72 px), e o círculo md fica com 40 × 40 px numa linha flex.