← Biblioteca componentes/dados/grade-indicadores/ficha.md CódigoTela cheia
dados/grade-indicadores

Grade de indicadores com variação

🟢 estavel complexidade baixa prefixo ind- indicadores kpi variacao big-numbers quatro-colunas relatorio dados acessibilidade css-puro sem javascript

Incluir · as linhas que se colam num site novo, depois do core (CLAUDE.md §10)

<link rel="stylesheet" href="componentes/dados/grade-indicadores/estilo.css">

markup.html · 144 linhas · 7,5 KB

<!-- ==========================================================================
     GRADE DE INDICADORES COM VARIAÇÃO
     Prefixo: ind-
     Requer: core (tokens, base, motion.css, motion.js) + estilo.css
     Sem script.js. Sem lib.

     A IDEIA. Quatro a seis indicadores em grade, e em cada um o número, o
     rótulo, a VARIAÇÃO no período e o período em que ela foi apurada. É a
     variação que separa esta peça da régua de números: "alta de 11,6% em 12
     meses" é dado com tempo dentro; "R$ 4,8 bi" sozinho é afirmação estática.
     O irmão prova/stats-contador-regua faz a afirmação estática (e conta);
     o irmão dados/painel-patrimonio é interface com abas que recalculam.
     Esta aqui não tem controle nenhum: é leitura.

     A DIREÇÃO NÃO É COR. Verde para cima e vermelho para baixo é a convenção
     e ela falha em três lugares: para quem não distingue as duas matizes (~8%
     dos homens), em impressão monocromática, e — o pior — quando "para cima"
     não quer dizer "bom". Nesta grade o prazo do diagnóstico CAI e é boa
     notícia. Por isso a direção viaja em quatro canais redundantes:

       1. a palavra em .ind-var-dir  ("alta de", "queda de", "sem mudança")
       2. a seta, girada pelo CSS a partir de data-ind-dir
       3. a cor da linha, também vinda de data-ind-dir
       4. o próprio texto do período, que nomeia a janela

     O canal 1 é o único obrigatório e é o que não pode ser apagado — ver o
     gotcha da ficha. A cor é o quarto, nunca o primeiro.

     data-ind-dir="alta|queda|estavel" é o ÚNICO gancho de estado, e vive no
     .ind-var. Sem ele a linha cai no neutro (seta de lado, cor apagada), que
     é o default seguro: um atributo escrito errado nunca inventa uma alta.

     A DATA DE REFERÊNCIA NÃO É OPCIONAL, e mora na própria peça — a pílula da
     barra, logo acima da grade. Indicador sem data envelhece em silêncio e
     passa a ser informação errada exibida com confiança. A ressalva fica no
     pé DO COMPONENTE, ao lado dos números que ela cobre, nunca no rodapé da
     página.

     ENTRADA. Um data-rv só, no .ind-grade: a grade é um objeto, não quatro
     animações. A seta tem transform próprio e por isso fica dentro do
     invólucro revelado, nunca no nó do data-rv (contrato §5).

     CONTEÚDO ILUSTRATIVO. Os quatro indicadores abaixo são números redondos
     de exemplo, para dimensionar a grade — não são apuração de casa nenhuma.
     Ao usar, troque pelo dado real, ponha a data de apuração na pílula e leia
     a seção Compliance da ficha antes de publicar.
     ========================================================================== -->

<section class="ind sec-deep sec-pad" id="indicadores">
  <div class="wrap">

    <div class="sec-head ind-head">
      <p class="eyebrow" data-rv>Indicadores</p>
      <h2 class="sec-title ttl">Onde a casa está, e o que <em>mudou</em></h2>
      <p class="sec-lead" data-rv data-d="1">Os quatro números que abrem o relatório consolidado. Cada um vem com a janela em que a variação foi apurada — sem janela, número não informa nada.</p>
    </div>

    <!-- BARRA DA GRADE
         Data de referência à esquerda, na primitiva .pill do core, e a
         procedência à direita. As duas coisas respondem perguntas diferentes:
         a pílula diz DE QUANDO é o número, a fonte diz DE ONDE ele vem.
         Quando algum indicador sai de série pública (IPCA, Selic, câmbio), a
         fonte deixa de ser opcional — ver Compliance na ficha. -->
    <div class="ind-barra" data-rv data-d="1">
      <p class="pill ind-data">Posição de 31/08/2026</p>
      <p class="ind-fonte">Apuração interna, revisada pelo comitê de investimentos</p>
    </div>

    <!-- A GRADE
         <ul role="list"> porque é uma lista de indicadores, e o role explica
         a semântica que o list-style:none do core tira no Safari.

         A ORDEM DENTRO DA CÉLULA é valor, rótulo, variação — e é ela que
         mantém a grade alinhada sem subgrid e sem script: o valor abre a
         célula (topo comum a todas) e a variação é empurrada para o pé por
         margin-top:auto, então o rótulo é o único que pode crescer, e cresce
         no vão do meio. Ver o comentário do estilo.css. -->
    <ul class="ind-grade" role="list" data-rv data-d="2">

      <li class="ind-cel">
        <p class="ind-valor">R$ 4,8 bi</p>
        <p class="ind-rotulo">Patrimônio sob assessoria</p>
        <p class="ind-var" data-ind-dir="alta">
          <span class="ind-var-seta" aria-hidden="true">
            <svg viewBox="0 0 14 14" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M7 12V2M3 6L7 2L11 6"/></svg>
          </span>
          <span class="ind-var-dir">alta de</span>
          <span class="ind-var-num">11,6%</span>
        </p>
        <p class="ind-var-per">em 12 meses</p>
      </li>

      <li class="ind-cel">
        <p class="ind-valor">312</p>
        <p class="ind-rotulo">Famílias e empresários</p>
        <p class="ind-var" data-ind-dir="alta">
          <span class="ind-var-seta" aria-hidden="true">
            <svg viewBox="0 0 14 14" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M7 12V2M3 6L7 2L11 6"/></svg>
          </span>
          <span class="ind-var-dir">alta de</span>
          <span class="ind-var-num">24</span>
        </p>
        <p class="ind-var-per">em 12 meses</p>
      </li>

      <!-- PARADO. Não há número: escrever "+0,0%" seria precisão falsa. A
           palavra ocupa o lugar do valor da variação, e o .ind-var-num some
           desta célula — é o caso "no máximo um por item" do bloco editorial. -->
      <li class="ind-cel">
        <p class="ind-valor">18</p>
        <p class="ind-rotulo">Assessores certificados</p>
        <p class="ind-var" data-ind-dir="estavel">
          <span class="ind-var-seta" aria-hidden="true">
            <svg viewBox="0 0 14 14" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M7 12V2M3 6L7 2L11 6"/></svg>
          </span>
          <span class="ind-var-dir">sem mudança</span>
        </p>
        <p class="ind-var-per">no trimestre</p>
      </li>

      <!-- A QUEDA QUE É BOA NOTÍCIA, e está aqui de propósito: é o argumento
           contra o par verde/vermelho. O prazo caiu três dias; pintar isso de
           vermelho afirmaria o contrário do que o dado diz. -->
      <li class="ind-cel">
        <p class="ind-valor">12</p>
        <p class="ind-rotulo">Dias até a política de investimentos</p>
        <p class="ind-var" data-ind-dir="queda">
          <span class="ind-var-seta" aria-hidden="true">
            <svg viewBox="0 0 14 14" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M7 12V2M3 6L7 2L11 6"/></svg>
          </span>
          <span class="ind-var-dir">queda de</span>
          <span class="ind-var-num">3 dias</span>
        </p>
        <p class="ind-var-per">em 12 meses</p>
      </li>

    </ul>

    <div class="ind-pe" data-rv data-d="3">
      <p class="nota ind-nota">Números ilustrativos, para dimensionar a peça: não representam carteira, resultado de cliente nem recomendação. Variação apurada é fato passado e não indica desempenho futuro.</p>
    </div>

  </div>
</section>

estilo.css · 247 linhas · 11,6 KB

/* ==========================================================================
   GRADE DE INDICADORES COM VARIAÇÃO — prefixo ind-
   Quatro a seis células em grade. Em cada uma: valor grande, rótulo em caixa
   alta, a variação com seta e palavra, e o período. Nenhum keyframe, nenhuma
   transição, nenhum script — a única coisa que se move é a entrada, e ela é
   o [data-rv] do core.

   Tokens usados:
     --fg               tinta do valor
     --fg-muted         rótulo, período, fonte, ressalva
     --rule             filete no topo de cada célula
     --accent           default de --ind-alta
     --accent-2         default de --ind-queda
     --display          face do valor e da linha de variação
     --sans             face do rótulo, do período e da ressalva
     --radius-pill      só via .pill do core, na data de referência
   Primitivas do core (usadas, não recopiadas): .wrap .sec-pad .sec-head
   .eyebrow h2.sec-title .sec-lead .pill

   O QUE MANTÉM A GRADE ALINHADA, e é a decisão de forma do componente.
   Número que dança na vertical estraga a leitura das seis células de uma vez,
   e o que faz um número dançar é o rótulo acima dele quebrar em duas linhas
   numa célula e em uma noutra. Duas saídas existiam:

     a) grid-template-rows:subgrid, com cada célula assinando as linhas do pai;
     b) ordenar a célula de modo que o vão elástico fique DEPOIS do valor.

   Escolhi (b), e não é economia de suporte — é que ela alinha as DUAS pontas
   de graça. O valor abre a célula, então o topo dele é o topo da célula, que
   é o mesmo em todas (item de grade nasce `stretch`). A variação é empurrada
   para o pé por `margin-top:auto`, então o pé dela também é comum. Sobra o
   rótulo no meio, e é ele que absorve a folga — que é exatamente o slot que
   pode ter uma linha ou três sem prejuízo. Subgrid resolveria o mesmo com
   uma dependência de versão de navegador e um `grid-row: span 3` que precisa
   ser mantido em sincronia com o número de filhos da célula.

   O filete de `border-top` na célula (e não na grade) existe por isso: ele
   desenha a linha em que os valores se alinham. Fica cortado pelo vão entre
   colunas de propósito — um filete por coluna lê como cabeçalho de tabela de
   relatório, que é o registro certo aqui; um filete corrido de ponta a ponta
   mais os verticais fechariam uma moldura e a peça passaria a ler como
   tabela de verdade.

   NÃO EXISTE @media(prefers-reduced-motion) NESTE ARQUIVO, e a ausência é
   deliberada: não há transição, animação nem transform animado para
   desligar. O único movimento da peça é a entrada por [data-rv], que o
   core/motion.css já desliga. A rotação da seta é estática — ela nunca
   interpola de um ângulo para outro.
   ========================================================================== */

/* Só a largura da coluna do cabeçalho: o resto — inclusive a centralização dos filhos vem do .sec-head do core (desde 2026-09-29) —
   vem da primitiva. */
.ind-head{--head-max:700px}

/* ---------------------------------------------------------------- a barra */
/* Data de referência à esquerda, procedência à direita. As duas respondem
   perguntas diferentes e nenhuma das duas é decoração: a pílula diz DE
   QUANDO o número é, a fonte diz DE ONDE ele veio. Indicador sem data
   envelhece em silêncio, e é por isso que ela mora aqui — encostada na
   grade — e não na letra miúda do rodapé da página. */
.ind-barra{
  margin-top:clamp(34px,5vh,56px);
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  justify-content:space-between;
  gap:12px 22px;
}
.ind-fonte{
  font-family:var(--sans);
  font-size:12.5px;line-height:1.5;letter-spacing:normal;
  color:var(--fg-muted);
  max-width:52ch;
}

/* ---------------------------------------------------------------- a grade */
/* --ind-cols é o único ajuste de quantidade no desktop: 4 para quatro ou
   cinco indicadores, 3 para seis (duas fileiras de três). Acima de seis a
   peça deixa de ser grade e vira tabela — e para tabela existe
   listas/tabela-comparativa.

   O vão da fileira é bem maior que o da coluna, e tem de ser: é ele que faz
   o filete da segunda fileira ler como uma banda nova em vez de um risco
   solto embaixo da primeira. */
.ind-grade{
  --ind-cols:4;
  --ind-gap-col:clamp(18px,2.4vw,34px);
  --ind-gap-row:clamp(34px,4.6vh,54px);
  --ind-valor-fs:clamp(28px,3.2vw,44px);
  margin-top:clamp(20px,2.8vh,32px);
  display:grid;
  grid-template-columns:repeat(var(--ind-cols),minmax(0,1fr));
  column-gap:var(--ind-gap-col);
  row-gap:var(--ind-gap-row);
}

/* flex-direction:column em vez de grid dentro da célula: o `margin-top:auto`
   do §"o que mantém a grade alinhada" depende de haver folga distribuível, e
   em coluna flex ela existe sem declarar linha nenhuma. */
.ind-cel{
  display:flex;
  flex-direction:column;
  border-top:1px solid var(--rule);
  padding-top:clamp(15px,1.8vw,22px);
}

/* ---------------------------------------------------------------- o valor */
.ind-valor{
  font-family:var(--display);font-weight:700;
  font-size:var(--ind-valor-fs);
  line-height:.98;letter-spacing:-.03em;
  color:var(--fg);

  /* É dado tabular: seis células de números com avanço proporcional leem
     ragged mesmo lado a lado, porque o "1" estreito abre um vão que o "8"
     não abre. Aqui não há contagem para tremer (o componente não tem
     script) — o motivo é só a mancha. Se a --display do tema não tiver
     algarismos tabulares, nada quebra: só volta a ficar ragged. */
  font-variant-numeric:tabular-nums;

  /* Nunca quebra. Um valor em duas linhas empurra o rótulo e a célula deixa
     de parecer irmã das outras; e o transbordo é para ser visto e corrigido
     — o teto medido está na ficha. */
  white-space:nowrap;
}

/* O slot elástico da célula: uma a três linhas, e é aqui que a diferença de
   altura entre as células é absorvida. Sem max-width — a célula já é estreita
   e o rótulo tem de poder usar a largura toda. */
.ind-rotulo{
  font-family:var(--sans);
  font-size:11.5px;line-height:1.5;letter-spacing:.13em;text-transform:uppercase;
  color:var(--fg-muted);
  margin-top:clamp(10px,1.2vw,14px);
}

/* ------------------------------------------------------------ a variação */
/* margin-top:auto empurra a linha para o pé da célula: com isso o pé da
   variação é comum a todas as células da fileira, do mesmo jeito que o topo
   do valor é. Precedente do gesto: .wealth-close do dados/painel-patrimonio.

   A COR VEM DE FALLBACK DENTRO DO var(), não de uma declaração em .ind. A
   diferença é a que o core documenta no --display-xl: declarado num
   ancestral do componente, o default venceria um --ind-alta que o TEMA
   pusesse no :root, e o parafuso pareceria não funcionar. Assim as duas
   portas ficam abertas — tema no :root, ou instância no .ind.

   POR QUE O DEFAULT NÃO É VERDE E VERMELHO. Não é só daltonismo (~8% dos
   homens) nem impressão monocromática: é que direção não é veredito. Nesta
   mesma grade o patrimônio SOBE (boa notícia) e o prazo do diagnóstico CAI
   (boa notícia também) — e num painel de mercado o IPCA subindo é notícia
   ruim. Um par verde/vermelho afirmaria julgamento que o dado não carrega.
   Os defaults são as duas matizes que todo tema já tem de definir, --accent
   e --accent-2: duas direções, duas cores de marca, nenhum veredito. Projeto
   que queira a convenção sobrescreve as duas variáveis, e a ficha diz em que
   caso isso é honesto.

   O NEUTRO É O DEFAULT DO SELETOR, de propósito: sem data-ind-dir, ou com
   valor escrito errado, a linha cai em "estável" com a seta de lado. Um
   atributo com typo nunca inventa uma alta. */
.ind-var{
  --ind-seta:90deg;
  color:var(--ind-estavel,var(--fg-muted));
  margin-top:auto;
  padding-top:clamp(14px,1.8vw,20px);
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  /* 5px é o espaço de palavra desta face neste corpo: "alta de" e o número
     leem como uma frase, não como duas colunas — com 7px a linha lia como
     três tokens soltos. A seta soma 2px de margin-right logo abaixo, para
     ficar com 7px: ela é outra classe de sinal, e colada ao texto na mesma
     medida das palavras viraria acento da primeira letra. */
  gap:2px 5px;
  font-family:var(--display);font-weight:700;
  font-size:13.5px;line-height:1.25;letter-spacing:normal;
}
.ind-var[data-ind-dir="alta"]{--ind-seta:0deg;color:var(--ind-alta,var(--accent))}
.ind-var[data-ind-dir="queda"]{--ind-seta:180deg;color:var(--ind-queda,var(--accent-2))}

/* UMA seta no markup, três direções no CSS. O ângulo vem do mesmo atributo
   que a cor, então os dois nunca podem divergir — que é a única divergência
   que o componente consegue impedir por construção (a palavra do
   .ind-var-dir, não; ver o gotcha da ficha).

   O transform mora AQUI, num descendente, e nunca no nó que leva o
   [data-rv]: o core termina o reveal com `[data-rv].in{transform:none}`,
   que vale (0,2,0) e apagaria a rotação em silêncio quando a dobra entrasse
   (contrato §5). O [data-rv] está no .ind-grade, três níveis acima. */
.ind-var-seta{
  flex:0 0 auto;
  display:inline-grid;place-items:center;
  width:12px;height:12px;
  margin-right:2px;
  transform:rotate(var(--ind-seta));
}
.ind-var-num{font-variant-numeric:tabular-nums}

/* O período não é direcional e por isso não recebe a cor: ele nomeia a
   janela em que a variação foi apurada, e a janela é fato, não movimento. */
.ind-var-per{
  font-family:var(--sans);font-weight:300;
  font-size:12.5px;line-height:1.4;letter-spacing:normal;
  color:var(--fg-muted);
  margin-top:5px;
}

/* ------------------------------------------------------------------- o pé */
/* A ressalva fica ao lado dos números que ela cobre. No rodapé da página ela
   é, na prática, invisível — e é justamente aqui que a pessoa está lendo o
   número. */
.ind-pe{margin-top:clamp(28px,4vh,48px)}
/* A tipografia inteira é a da primitiva .nota do core — este componente já
   escrevia a mesma receita à mão, inclusive o letter-spacing:normal. Resta a
   medida: 76ch acompanha a grade de seis indicadores. */
.ind-nota{--nota-max:76ch}

/* ---------------------------------------------------------------- mobile */
/* DUAS COLUNAS, NÃO UMA. Indicador é curto — um valor de poucos glifos, um
   rótulo de duas a cinco palavras — e em coluna única cada célula gasta a
   largura inteira do celular para mostrar "312". A grade de seis viraria uma
   rolagem de seis telas. Em duas colunas a fileira continua legível e a
   peça continua sendo uma GRADE, que é o que ela é.

   As colunas vão cravadas, sem passar por --ind-cols, e isso é de propósito:
   estilo inline vence media query, então quem escrevesse --ind-cols:4 no
   style de uma instância quebraria o mobile sem querer. O corpo do valor
   segue por variável porque ali o ajuste por projeto é legítimo — é medida,
   não estrutura. Mesma divisão que o prova/stats-contador-regua faz. */
@media(max-width:900px){
  .ind-grade{
    grid-template-columns:repeat(2,minmax(0,1fr));
    --ind-valor-fs:clamp(26px,4.4vw,38px);
  }
}
/* Abaixo de 560px o vão da coluna encolhe antes do corpo do número: é o vão
   que sobra, não o número. Medido a 320px de janela, que é o piso realista:
   "R$ 4,8 bi" (o valor mais largo que a ficha admite) ocupa 92,6px numa
   célula de 125,5px — 33px de sobra, com duas colunas de pé. */
@media(max-width:560px){
  .ind-grade{
    --ind-gap-col:clamp(14px,4vw,20px);
    --ind-valor-fs:clamp(24px,7vw,32px);
  }
  .ind-var{font-size:13px}
  .ind-rotulo{letter-spacing:.1em}
}

Uma grade de quatro a seis células. Em cada uma: o valor em corpo grande, o rótulo em caixa alta, a variação com seta e palavra, e o período em que ela foi apurada. Acima da grade, uma barra com a data de referência e a procedência; abaixo, a ressalva. Não há controle nenhum — a peça é leitura, não ferramenta. CSS puro, sem script.js.

O que ela resolve que uma régua de números não resolve: a variação. "R$ 4,8 bi" é uma afirmação estática; "R$ 4,8 bi, alta de 11,6% em 12 meses, posição de 31/08/2026" é dado com tempo dentro — tem magnitude, direção, janela e validade. É a diferença entre dizer quanto e dizer quanto, e para onde.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
Olho (.eyebrow) 1 a 3 palavras. "Indicadores", "A casa em números". Opcional.
Título (.sec-title) 4 a 9 palavras, uma em <em>. Nomeia o que a grade mede e que houve mudança: "Onde a casa está, e o que mudou". Não repete nenhum dos números.
Apoio (.sec-lead) 1 parágrafo, 15 a 32 palavras. Diz de onde vêm os números e por que cada um traz janela. Opcional, mas é aqui que a grade ganha credibilidade.
Data de referência (.ind-data) 12 a 26 caracteres. "Posição de 31/08/2026". Obrigatória, em toda variação.
Fonte (.ind-fonte) 3 a 9 palavras. Diz de onde o número vem, enquanto a data diz de quando ele é. Opcional só quando tudo é apuração própria.
Valor (.ind-valor) 1 a 9 caracteres. 312, 4,52%, R$ 4,8 bi. Nove glifos é o teto medido. Não quebra linha. Unidade longa ("dias") mora no rótulo ou na variação, nunca aqui.
Rótulo (.ind-rotulo) 2 a 6 palavras. Substantivo, sem verbo: "Patrimônio sob assessoria", não "Já assessoramos". Minúsculas no HTML — a caixa alta é do CSS.
Direção (data-ind-dir no .ind-var) alta, queda ou estavel. Não é texto: é o gancho de estado. Ausente ou com typo cai no neutro.
Palavra da direção (.ind-var-dir) 1 a 3 palavras. "alta de", "queda de", "sem mudança". Não é opcional — ver gotchas.
Variação (.ind-var-num) 1 a 10 caracteres. "11,6%", "2,4 p.p.", "3 dias". Ausente quando o indicador está parado. Sem sinal por padrão: a palavra já diz a direção.
Período (.ind-var-per) 2 a 4 palavras. "em 12 meses", "no trimestre".
Ressalva (.ind-nota) 2 a 3 frases, no pé do componente.

O que mantém a grade coesa é o paralelismo das quatro linhas: todo indicador tem valor, rótulo, direção e janela, e nenhum tem duas janelas diferentes na mesma célula. Misturar "em 12 meses" com "no trimestre" entre células é legítimo e comum (nem tudo se apura no mesmo ritmo); o que não pode é uma célula sem janela nenhuma.

Uma grade por página, duas no máximo, e a segunda só quando os assuntos são distintos e nomeados (a casa numa, o cenário na outra). Duas grades do mesmo assunto viram uma tabela partida em duas.

O rótulo é o único slot elástico, e isso é estrutural, não conselho: a folga de altura entre as células é absorvida ali (uma a três linhas), e é por isso que os valores continuam alinhados. Escrever rótulos de tamanho parecido melhora a mancha, mas não é o que segura o alinhamento — ver "Como usar".

O que entra na grade, e o que não entra

Serve Não serve
estoque: patrimônio sob assessoria, famílias, assessores, instituições rentabilidade de carteira, própria ou de cliente
mercado, com fonte: IPCA em 12 meses, Selic, câmbio comparação implícita com concorrente ("2× mais que a média")
operação: prazo até a política, cobertura do comitê número sem data de apuração
custo: taxa média de honorários acordada número sem janela de variação

Especificação dos assets

Nenhum. A peça é tipografia, um filete por célula e uma seta de 12px em SVG inline, desenhada com currentColor — não há imagem, ícone de terceiro nem fonte extra.


Como usar

<link rel="stylesheet" href="core/tokens.css">
<link rel="stylesheet" href="core/temas/<projeto>.css">
<link rel="stylesheet" href="core/base.css">
<link rel="stylesheet" href="core/motion.css">
<link rel="stylesheet" href="componentes/dados/grade-indicadores/estilo.css">
...
<script src="core/motion.js"></script>

Cole o markup.html, troque os números, os rótulos e a data. Não há script.js. Depende do core e de mais nada.

O alinhamento, e por que ele não usa subgrid

Número que dança na vertical estraga a leitura das seis células de uma vez. O que faz um número dançar é o rótulo acima dele quebrar em duas linhas numa célula e em uma noutra. Havia duas saídas:

Está feito pelo segundo caminho, e o motivo não é suporte de navegador — é que ele alinha as duas pontas de graça:

  1. o valor abre a célula, então o topo dele é o topo da célula, que é o mesmo em todas (item de grade nasce stretch);
  2. a variação leva margin-top:auto, então o pé dela também é comum;
  3. sobra o rótulo no meio, e é ele que absorve a folga.

Medido a 1440 no tema liberta-wealth, com o rótulo da quarta célula quebrando em duas linhas e os outros três em uma:

célula 1 célula 2 célula 3 célula 4
topo do .ind-valor 488,06 488,06 488,06 488,06
linhas do .ind-rotulo 1 1 1 2
topo do .ind-var 579,66 579,66 579,66 579,66
pé do .ind-var 616,54 616,54 616,54 616,54

Subgrid resolveria o mesmo, com uma dependência de versão de navegador e um grid-row: span 3 que precisaria ser mantido em sincronia com o número de filhos da célula — e que quebraria em silêncio no dia em que alguém acrescentasse uma quinta linha à célula.

O filete de border-top vive na célula, não na grade, e é ele que desenha a linha em que os valores se alinham. Fica cortado pelo vão entre colunas de propósito: um filete por coluna lê como cabeçalho de tabela de relatório, que é o registro certo aqui. Filete corrido de ponta a ponta somado aos verticais fecharia uma moldura, e a peça passaria a ler como tabela de verdade — o mesmo alerta que a ficha do stats-contador-regua faz.

O gancho de estado

Um só, e vive no .ind-var:

<p class="ind-var" data-ind-dir="alta">
  <span class="ind-var-seta" aria-hidden="true"><svg …/></span>
  <span class="ind-var-dir">alta de</span>
  <span class="ind-var-num">11,6%</span>
</p>
<p class="ind-var-per">em 12 meses</p>
data-ind-dir seta cor da linha
alta 0° (para cima) --ind-alta, default var(--accent)
queda 180° (para baixo) --ind-queda, default var(--accent-2)
estavel 90° (de lado) --ind-estavel, default var(--fg-muted)
ausente, ou escrito errado 90° --ind-estavel

Uma seta no markup, três direções no CSS. O <svg> é sempre a mesma flecha para cima; o ângulo sai de --ind-seta, que as regras de atributo escrevem. Como cor e ângulo vêm do mesmo atributo, os dois nunca podem divergir.

Variáveis locais

As três cores entram como fallback dentro do var(), e não como declaração em .ind — mesma forma do --display-xl do core. Declaradas num ancestral, elas venceriam um --ind-alta que o tema pusesse no :root, e o parafuso pareceria não funcionar. Assim as duas portas ficam abertas: tema no :root, ou instância no .ind.

Variável Default O quê
--ind-alta var(--accent) cor da linha de variação em alta
--ind-queda var(--accent-2) cor da linha em queda
--ind-estavel var(--fg-muted) cor da linha parada, e o neutro de segurança

No .ind-grade:

Variável Default O quê
--ind-cols 4 colunas no desktop. 3 para seis indicadores
--ind-gap-col clamp(18px,2.4vw,34px) vão entre colunas
--ind-gap-row clamp(34px,4.6vh,54px) vão entre fileiras — maior de propósito
--ind-valor-fs clamp(28px,3.2vw,44px) corpo do valor

Variações

Variação Como
6 indicadores --ind-cols:3. Medido a 1440: três colunas de 342,7px, duas fileiras, topo do valor batendo dentro de cada fileira (488,06 e 686,18)
5 indicadores deixe --ind-cols:4. A quinta abre a segunda fileira sozinha e lê como continuação, porque o filete dela nasce no mesmo lugar da coluna 1
Forma compacta, só com o sinal escreva o sinal no .ind-var-num (+11,6%) e ponha class="ind-var-dir visually-hidden" na palavra. Nunca apague o .ind-var-dir
Convenção verde/vermelho --ind-alta e --ind-queda no tema. Só é honesto quando toda a grade tem a mesma valência — ver Compliance
Valores que contam ao entrar data-stats no .ind-grade e data-count em cada .ind-valor, mais o script.js do prova/stats-contador-regua (ele não conhece nenhuma classe stat-). Não recomendado — ver gotchas

Compliance

A peça exibe número, e por isso pisa em terreno regulado. Antes de publicar, o consumidor tem de trocar quatro coisas e conferir uma quinta.

1. Os números. Os quatro do markup.html são ilustrativos — números redondos de exemplo, marcados como tal em comentário HTML e na ressalva visível. Não são apuração de casa nenhuma.

2. A data de referência. .ind-data é obrigatória e não é opcional em nenhuma variação. Indicador sem data envelhece em silêncio: no dia seguinte à apuração ele ainda está certo, três trimestres depois ele é informação errada exibida com confiança, e nada na página acusa. Ela fica na peça, encostada na grade, e não na letra miúda do rodapé.

3. A fonte, quando o número vem de fora. Com qualquer série externa — IPCA, Selic, câmbio, índice de mercado — o .ind-fonte deixa de ser opcional e nomeia a série e o divulgador. Apuração própria pode dispensar, mas dizer "apuração interna" já é informação.

4. O que pode ser indicador. Estoque (patrimônio sob assessoria, famílias, assessores, instituições), mercado com fonte (IPCA, Selic, câmbio), operação (prazo até a política, cobertura do comitê) e custo (taxa média de honorários acordada). Nunca rentabilidade de carteira, própria ou de cliente, nem em uma célula. Uma grade de rentabilidade com seta verde é promessa de resultado com cara de dado, que é o pior formato possível para ela.

5. A ressalva, no pé do componente. Duas frases obrigatórias: que os números não representam carteira, resultado de cliente nem recomendação, e que variação apurada é fato passado e não indica desempenho futuro.

Por que o default não é verde e vermelho

Não é (só) daltonismo. O par verde/vermelho falha em três lugares, e o terceiro é o que decide:

  1. para quem não distingue as duas matizes — a deficiência mais comum, em torno de 8% dos homens;
  2. em impressão monocromática, que é como um relatório de assessoria costuma chegar impresso;
  3. quando "para cima" não quer dizer "bom" — e é o caso na maioria das grades honestas.

Na grade de exemplo o patrimônio sobe (boa notícia) e o prazo do diagnóstico cai (boa notícia também). Numa grade de mercado, o IPCA subindo é notícia ruim e a Selic subindo é notícia mista. Direção não é veredito. Um par verde/vermelho afirmaria um julgamento que o dado não carrega — e essa é uma questão de compliance antes de ser de acessibilidade: pintar "IPCA +0,4 p.p." de verde é induzir leitura favorável de um indicador desfavorável.

Por isso os defaults saem de --accent e --accent-2, as duas matizes que todo tema já tem de definir: duas direções, duas cores de marca, nenhum veredito. E por isso a direção viaja em quatro canais redundantes — palavra, seta, cor e período —, com a cor sendo o quarto e nunca o primeiro.

Se o projeto quiser mesmo a convenção, ela é honesta em um caso: quando todos os indicadores da grade têm a mesma valência (um painel só de estoque da casa, em que subir é sempre bom). Aí são duas variáveis no tema, e o dever de conferir contraste passa a ser dele.

Contraste, medido

A cor de direção cai em texto, não num ícone: o tema deve contraste a ela. Medido a 1440 no liberta-wealth, com a seção em .sec-deep (fundo rgb(24,24,24)):

Elemento Cor Contraste
.ind-valor --fg 15,6:1
.ind-var em alta --accent (sage) 10,8:1
.ind-var em queda --accent-2 (tan) 6,1:1
.ind-var parado --fg-muted 5,4:1
.ind-rotulo, .ind-nota --fg-muted 5,4:1

Todos acima de 4,5:1. A conferência não é dispensável em outro tema: o --accent-2 do core/tokens.css é um cinza de espera (#6e6e6e) e dá 3,64:1 sobre o .sec-deep neutro (#131313) — abaixo de AA para 13,5px. Isso é propriedade do placeholder, não do componente (todo uso de --accent na biblioteca depende do tema), mas o efeito aqui é pior que o normal, porque o texto tingido é justamente o dado. Projeto cujo --accent-2 seja fraco tem duas saídas: melhorar o token, ou --ind-queda:var(--fg) — a seta e a palavra continuam dando a direção, e só a matiz se perde.


Gotchas


Histórico

Projeto Onde Data Diferenças
autoral — 2026-09-09 Construído do zero na biblioteca. Sem referência única e sem uma linha copiada.

O padrão — grade de quatro a seis células com cifra, rótulo curto e a mudança no período — é o cabeçalho padrão de relatório de gestora e de banco privado, o "KPI card com delta" de painel de BI (Tableau, Looker, Metabase) e a página de fatos e números de casa de investimento. As famílias de exemplo entraram como proporção e ritmo de leitura; nada mais.

Normalizações aplicadas na tradução para o contrato:

Nas famílias de exemplo Aqui Por quê
delta em verde/vermelho, só cor palavra + seta + cor + janela, quatro canais daltonismo, impressão monocromática e, sobretudo, direção não ser veredito (ver Compliance)
token semântico de sucesso/erro --ind-alta / --ind-queda / --ind-estavel, com default em var(--accent) / var(--accent-2) / var(--fg-muted) a biblioteca não tem --pos/--neg, e criar um é decisão de arquitetura (§3). Ficou em aberto — ver abaixo
alinhamento por subgrid ou por script medindo altura ordem da célula + margin-top:auto alinha as duas pontas sem dependência de versão de navegador e sem um span N para manter em sincronia
data de apuração em rodapé, ou ausente .ind-data obrigatória, na barra da grade indicador sem data envelhece em silêncio
ícone por indicador (cofre, casa, pessoas) nenhum ícone de dashboard não é a voz da casa, e o rótulo já diz o que o número é
card com fundo e sombra por célula um filete de border-top por célula o filete desenha a linha em que os valores se alinham; card e sombra não pertencem a um tema flat
cifra contando ao carregar nenhuma contagem a variação não conta, e meia célula animando lê como bug. A porta continua aberta em duas linhas
cor crua no delta só token §3

Divergências conhecidas vs. produção: nenhuma — o componente nasceu aqui.

Em aberto — token semântico de positivo/negativo

A biblioteca não tem --pos/--neg, e o overlay/toast-aviso esbarrou no mesmo buraco antes: resolveu sem cor nenhuma, diferenciando os tons por ícone e por role de acessibilidade, e registrou na ficha que --tst-ok/--tst-atencao seriam "cor crua disfarçada de variável".

Este componente não pode repetir aquela solução, e a diferença é real: o toast tem três tons de aviso, e a informação do aviso é o texto; aqui a direção é parte do dado, e a convenção de tingi-la existe em todo painel de indicador do mundo. Então a saída foi a intermediária: custom property prefixada, com default derivado de tokens que já existem (--accent, --accent-2, --fg-muted), nenhuma cor crua, e a direção legível sem cor nenhuma.

A recomendação para a casa, relatada e não implementada: um par --pos/--neg global provavelmente não vale a pena, por três motivos:

  1. Ele nomearia uma coisa que a biblioteca não tem. --accent é cor de marca, e "bom" não é uma categoria de marca. Um --pos global obrigaria todo tema a decidir o que é "bom" em abstrato.
  2. Direção não é valência, e um token chamado --pos empurra o consumidor exatamente para o erro que a seção Compliance descreve — pintar de verde um IPCA em alta.
  3. Duas peças, duas necessidades diferentes. O toast quer tom de aviso (sucesso/erro), esta grade quer direção de movimento. Um token só serviria mal às duas.

O que talvez valha, se uma terceira peça pedir: um par --dir-up/--dir-down — nomeando direção, não julgamento. Com duas peças, ainda não bateu o gatilho de três do §6. Fica registrado.