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.

BasePageState e BaseEmptyState ​

BaseEmptyState ocupa uma área sem conteúdo: diz o que vai aparecer ali e qual é o primeiro passo. BasePageState cobre os outros estados que toda tela prevê: carregando, erro, sem permissão e sessão expirada, com o texto padrão em português que a tela pode trocar.

Nenhuma medição lançada

As medições deste contrato aparecem aqui assim que o apoio lançar a primeira.

Uso ​

BaseEmptyState ​

  • Use quando a área carregou e não tem nada: lista, tabela, painel.
  • O título diz o que falta ("Nenhuma medição lançada"), não "Sem dados".
  • O texto diz quando o conteúdo aparece ou o que fazer. Vazio convida a agir.
  • Coloque no slot action uma ação só, a que preenche a área. Quem não pode agir (por exemplo, não tem permissão para lançar) vê o vazio sem botão.
  • Para busca sem resultado, diga o que foi buscado e ofereça limpar a busca:

Nenhum contrato com "Serra 2"

Confira a grafia ou busque pelo número do contrato.

BasePageState ​

  • Envolva o conteúdo da área com BasePageState e mude só o status. Com ready, ele mostra o slot padrão; nos outros estados, a mensagem toma o lugar do conteúdo.
  • loading: o esqueleto aparece depois de 300 ms (delay), para uma resposta rápida não piscar na tela. Desenhe a forma do conteúdo real no slot loading; sem ele, aparecem um título, três linhas e um bloco.
  • error: diz que não carregou e oferece "Tentar de novo" (evento retry). Passe o code para o usuário informar à STI; o texto padrão passa a apontar para ele. O código só aparece no erro.
  • forbidden: diz que a pessoa não tem acesso e a quem pedir. Não tem botão padrão; use o slot action para um link de volta, se fizer sentido.
  • expired: diz que a sessão expirou e oferece "Entrar de novo" (evento signIn).
  • Troque title e text quando o contexto permitir dizer mais, como "Não foi possível carregar os contratos".
  • Mostre o estado vazio dentro do ready, com BaseEmptyState: vazio não é erro.

Escolha um estado para ver a troca; "Tentar de novo" carrega por 1,5 s e mostra o conteúdo.

Não foi possível carregar os contratos

Tente de novo. Se o erro continuar, informe o código abaixo à STI.

Código ERR-CTR-504 · 21/10/2026 18:40

title e text trocam o texto de qualquer estado de problema que estiver na tela. Por isso o exemplo só passa o title quando o estado é erro: sem permissão e sessão expirada ficam com o texto padrão. O code aparece só no erro, então pode ficar ligado o tempo todo.

Texto padrão ​

EstadoTítuloTextoAção
loading— (anuncia "Carregando")——
errorNão foi possível carregar as informaçõesTente de novo. Se o erro continuar, avise a STI. Com code: Tente de novo. Se o erro continuar, informe o código abaixo à STI.Tentar de novo
forbiddenVocê não tem acesso a esta páginaSe precisa dela para o seu trabalho, peça o acesso ao gestor da sua unidade.—
expiredSua sessão expirouPor segurança, a sessão termina depois de um tempo sem uso. Entre de novo para continuar.Entrar de novo

Estilo ​

PropriedadeToken
Espaço entre os itens--tj-space-3 (12 px); a ação fica --tj-space-2 mais longe
Margem interna do vazio--tj-space-12 em cima e embaixo, --tj-space-6 nos lados
Margem interna dos estados--tj-space-10 em cima e embaixo, --tj-space-6 nos lados
Caixa do ícone56 px no vazio, 48 px nos estados; raio --tj-radius-lg; ícone --tj-icon-lg
Ícone do vaziofundo --tj-layer-selected-01, cor --tj-icon-interactive, contorno --tj-border-selected-subtle
Ícone do errofundo --tj-support-error-subtle, cor --tj-support-error-text
Ícone de sem permissãofundo --tj-layer-accent-01, cor --tj-text-secondary
Ícone de sessão expiradafundo --tj-support-warning-subtle, cor --tj-support-warning-text
Títuloestilo h3 do BaseHeading, --tj-text-primary
Texto--tj-text-helper, até 380 px no vazio e 360 px nos estados
Código para a STI--tj-font-mono, --tj-text-xs, --tj-text-secondary

No tema escuro os tokens trocam sozinhos. No modo de alto contraste do sistema, a caixa do ícone ganha contorno. Diferenças em relação ao protótipo v3:

  • raio da caixa do ícone: o protótipo usa 16 e 14 px; aqui fica --tj-radius-lg (12 px), o maior raio da escala;
  • cor do ícone do vazio: o protótipo usa a cor de link (--tj-link-primary); aqui fica o papel de ícone, --tj-icon-interactive;
  • tamanho do ícone dos estados: o protótipo usa 22 px; aqui fica --tj-icon-lg (24 px), o mesmo do vazio, porque a escala de ícones não tem 22 px;
  • código para a STI: o protótipo o coloca depois do botão; aqui ele fica logo abaixo do texto, que aponta para ele.

Código ​

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

import type { PageStateStatus } from '@lab-inovacao/design-system'

const status = ref<PageStateStatus>('loading')
const errorCode = ref<string>()
const contracts = ref<Contract[]>([])

async function load(): Promise<void> {
  status.value = 'loading'
  try {
    contracts.value = await fetchContracts()
    status.value = 'ready'
  } catch (error) {
    errorCode.value = errorReference(error)
    status.value = isForbidden(error) ? 'forbidden' : isExpired(error) ? 'expired' : 'error'
  }
}

onMounted(load)
</script>

<template>
  <BasePageState
    :status="status"
    :code="errorCode"
    :title="status === 'error' ? 'Não foi possível carregar os contratos' : undefined"
    loading-label="Carregando contratos"
    ready-label="Contratos carregados"
    @retry="load"
    @sign-in="goToLogin"
  >
    <template #loading>
      <BaseSkeleton size="lg" width="30%" />
      <BaseSkeleton variant="rect" height="320px" />
    </template>
    <ContractTable v-if="contracts.length" :contracts="contracts" />
    <BaseEmptyState
      v-else
      icon="file-earmark-text"
      title="Nenhum contrato cadastrado"
      text="Os contratos de obra aparecem aqui assim que o primeiro for cadastrado."
    >
      <template #action>
        <BaseButton icon="plus-lg" @click="newContract">Novo contrato</BaseButton>
      </template>
    </BaseEmptyState>
  </BasePageState>
</template>

BaseEmptyState ​

PropriedadeTipoPadrãoDescrição
titlestring—Obrigatório. O que vai aparecer ali
textstring—Quando o conteúdo aparece ou qual o primeiro passo
iconstring—Nome do ícone do Bootstrap Icons, sem bi-; decorativo
headingLevel1 a 62Nível do título no sumário da página; o visual não muda

Slots: action (a ação que preenche a área) e o padrão (conteúdo extra abaixo do texto). Sem eventos.

BasePageState ​

PropriedadeTipoPadrãoDescrição
status'loading' | 'ready' | 'error' | 'forbidden' | 'expired'—Obrigatório. Estado da área
titlestringtexto padrão do estadoTroca o título do estado de problema que estiver na tela (erro, sem permissão ou sessão expirada); passe só para o estado que ele descreve
textstringtexto padrão do estadoTroca o texto do estado de problema que estiver na tela, como o title
actionLabelstring"Tentar de novo" ou "Entrar de novo"Troca o texto do botão padrão
codestring—Código para a STI, mostrado como "Código …" só no erro
headingLevel1 a 62Nível do título da mensagem
loadingLabelstring'Carregando'Anunciado ao leitor de tela quando o esqueleto aparece
readyLabelstring'Conteúdo carregado'Anunciado quando a carga termina com o conteúdo
delaynumber300Milissegundos antes de mostrar o esqueleto; 0 mostra na hora

Eventos: retry (botão do erro) e signIn (botão da sessão expirada; no template, @sign-in).

Slots: o padrão (conteúdo, com ready), loading (forma do esqueleto) e action (troca o botão padrão de qualquer estado de problema).

Texto em branco em title, text ou actionLabel volta ao texto padrão, para a tela nunca ficar sem mensagem.

Acessibilidade ​

  • O título é um cabeçalho de verdade (h2 por padrão). Ajuste headingLevel ao sumário da página e não pule nível.
  • O ícone é decorativo (aria-hidden="true"): o título diz o que houve. A cor do ícone nunca é o único sinal.
  • BasePageState mantém uma região role="status" sempre na página, fora da área com aria-busy. Ela entra vazia e só depois recebe o texto, porque uma região que já entra com texto costuma não ser anunciada:
    • ao carregar, anuncia o loadingLabel quando o esqueleto aparece (uma resposta mais rápida que o delay não anuncia nada);
    • ao terminar, anuncia o readyLabel, só se houve carga antes;
    • nos estados de problema, anuncia o título e o texto, para a pessoa ouvir o que houve e como seguir;
    • um estado de problema encerra a carga: voltar dele para o conteúdo não anuncia o readyLabel;
    • o texto sai da região depois de 5 segundos, para não ser lido de novo na leitura corrida da página.
  • A região é educada (role="status"), mesmo no erro depois de "Tentar de novo": o foco vai para o título do erro e o leitor de tela já o lê; um role="alert" interromperia essa leitura.
  • A área do conteúdo recebe aria-busy="true" só enquanto carrega; com o conteúdo ou com um erro, o atributo sai.
  • Os botões são BaseButton: Enter e Espaço acionam, e o foco aparece com --tj-ring.
  • Quando o botão com foco sai da tela (por exemplo, "Tentar de novo" passa o estado para carregando), o foco não cai no começo da página: vai para a área (tabindex="-1", sem anel) enquanto carrega ou com o conteúdo, e para o título da mensagem (tabindex="-1") nos estados de problema. Se o foco estava fora da área, o componente não mexe nele.
  • O esqueleto respeita prefers-reduced-motion (veja BaseSkeleton).
  • Testado com axe em cada estado: pronto, carregando antes e depois do atraso e com esqueleto próprio, erro com e sem código, sem permissão com e sem link no slot action e sessão expirada; e o vazio com e sem ícone, texto, ação e conteúdo extra.