Início Boas práticas Tema Idiomas e RTL App e cabeçalho Layout Tipografia Abas Menu e âncora Dropdown e popover Command e toggles Botão Campos Seleção e valores Combobox e menções Datas e calendário Árvore e cascata Transfer Upload Filtros Tabela Planilha KPI e estatística Badge e tag Listas e detalhes Gráficos Imagem e carrossel Ícones Diagramas Alertas e estados Progresso e passos Diálogo e toast Drawer e notificação Utilitários Overview Work queue Report Report com htmx Form / request Tabela administrativa Página de detalhe Formulário em diálogo Gráfico com dados do servidor Buscar… llms.txt Tema Tema Restaurar Copiar prompt Copiar snippet Prompt: para as instruções de um agente. Snippet: direto no templates/base.html. v0.11.0 Chart for dashboards: line, area, bar (vertical), bar-horizontal, pie and donut. Receita mensal vs. metaLinhas do Python em data, x = campo da data, series = colunas numéricas. goal desenha a linha de meta. Passe o mouse ou use as setas do teclado. <ab-chart id="c-receita" type="bar" x="mes" x-type="date" value-type="currency" goal="120000" goal-label="Meta" heading="Receita mensal" description="Faturamento bruto em 2026"></ab-chart> <script type="module"> const el = document.getElementById("c-receita"); // rows = [dict(r) for r in sql("select mes, receita from receita_mensal")] el.data = [ { mes: "2026-01-01", receita: 98400 }, { mes: "2026-02-01", receita: 104250 }, { mes: "2026-03-01", receita: 112900 }, { mes: "2026-04-01", receita: 108300 }, { mes: "2026-05-01", receita: 121700 }, { mes: "2026-06-01", receita: 126450 }, { mes: "2026-07-01", receita: 119800 }, { mes: "2026-08-01", receita: 131200 }, { mes: "2026-09-01", receita: 138900 }, { mes: "2026-10-01", receita: 142300 }, ]; el.series = [{ key: "receita", label: "Receita" }]; el.addEventListener("ab-point-click", (e) => abToast(`Abrir detalhes de ${e.detail.row.mes}`, { tone: "info" })); </script> Entradas vs. saídasDuas séries de barras ficam agrupadas lado a lado. color aceita um tom (ok, danger…) ou uma cor CSS. Uma série com type: “line” vira combo. <ab-chart id="c-caixa" x="mes" x-type="date" value-type="currency" heading="Fluxo de caixa" description="Clique na legenda para ocultar uma série"></ab-chart> <script type="module"> const el = document.getElementById("c-caixa"); const meses = ["2026-05-01", "2026-06-01", "2026-07-01", "2026-08-01", "2026-09-01", "2026-10-01"]; const entradas = [84200, 91500, 78300, 96800, 102400, 99100]; const saidas = [71800, 88900, 82100, 79400, 85600, 90300]; el.data = meses.map((mes, i) => ({ mes, entradas: entradas[i], saidas: saidas[i], saldo: entradas[i] - saidas[i] })); el.series = [ { key: "entradas", label: "Entradas", color: "ok" }, { key: "saidas", label: "Saídas", color: "danger" }, { key: "saldo", label: "Saldo", type: "line", color: "info" }, ]; </script> Despesas por categoriaDonut com o total no centro; a legenda mostra valor e percentual. Para pizza use type=“pie”. <ab-chart id="c-despesas" type="donut" x="categoria" value-type="currency" heading="Despesas de outubro"></ab-chart> <script type="module"> document.getElementById("c-despesas").data = [ { categoria: "Folha de pagamento", valor: 48200 }, { categoria: "Aluguel", valor: 12500 }, { categoria: "Fornecedores", valor: 18900 }, { categoria: "Impostos", valor: 9700 }, { categoria: "Marketing", valor: 6200 }, { categoria: "Outros", valor: 3100 }, ]; </script> Pedidos por statusstacked empilha as séries; normalized mostra a participação (100%). <ab-chart id="c-status" x="semana" stacked heading="Pedidos por semana"></ab-chart> <script type="module"> const el = document.getElementById("c-status"); el.data = [ { semana: "S36", entregue: 182, transito: 41, atrasado: 9 }, { semana: "S37", entregue: 195, transito: 38, atrasado: 14 }, { semana: "S38", entregue: 171, transito: 52, atrasado: 21 }, { semana: "S39", entregue: 208, transito: 44, atrasado: 7 }, { semana: "S40", entregue: 214, transito: 39, atrasado: 11 }, ]; el.series = [ { key: "entregue", label: "Entregue", color: "ok" }, { key: "transito", label: "Em trânsito", color: "info" }, { key: "atrasado", label: "Atrasado", color: "warn" }, ]; </script> Top 10 clientestype=“bar-horizontal” para rankings com rótulos longos. <ab-chart id="c-top" type="bar-horizontal" x="cliente" value-type="currency" height="360" heading="Maiores clientes (12 meses)"></ab-chart> <script type="module"> document.getElementById("c-top").data = [ { cliente: "Clínica Bem Viver", total: 184300 }, { cliente: "Padaria Pão Dourado", total: 152900 }, { cliente: "Transportadora Rota Sul", total: 139400 }, { cliente: "Escola Pequeno Príncipe", total: 118700 }, { cliente: "Oficina Rota 66", total: 97200 }, { cliente: "Mercado Bom Preço", total: 88600 }, { cliente: "Construtora Alicerce Forte Ltda.", total: 74100 }, { cliente: "Farmácia Saúde Já", total: 61800 }, { cliente: "Hotel Mar Azul", total: 52300 }, { cliente: "Studio Pilates Equilíbrio", total: 39900 }, ]; </script> Saldo diárioÁrea com eixo de datas diárias (rótulos 03/10). As datas “YYYY-MM-DD” são ordenadas automaticamente. <ab-chart id="c-saldo" type="area" x="dia" x-type="date" value-type="currency" heading="Saldo em conta" description="Últimos 60 dias"></ab-chart> <script type="module"> const rows = []; let saldo = 182000; for (let i = 0; i < 60; i++) { const d = new Date(2026, 8, 7 + i); saldo += Math.round(Math.sin(i / 4) * 9000 + (i % 7 === 4 ? -21000 : 3200)); rows.push({ dia: `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`, saldo }); } document.getElementById("c-saldo").data = rows; </script> DRE em cascatatype=“waterfall”: cada linha é uma barra, na ordem recebida. kind “change” (padrão) é um movimento com sinal e parte do saldo acumulado; “subtotal” e “total” são o saldo informado e partem do zero, sem recálculo. O tooltip e “Ver dados” mostram variação, saldo anterior e saldo final. <ab-chart id="c-dre" type="waterfall" x="conta" value-type="currency" heading="Resultado do trimestre" description="Empresa fictícia, valores ilustrativos"></ab-chart> <script type="module"> const el = document.getElementById("c-dre"); // rows = [dict(r) for r in sql("select conta, valor as value, tipo as kind from dre order by ordem")] el.data = [ { conta: "Receita bruta", value: 482000, kind: "change" }, { conta: "Deduções e impostos", value: -57840 }, { conta: "Receita líquida", value: 424160, kind: "subtotal" }, { conta: "Custo dos serviços", value: -168500 }, { conta: "Lucro bruto", value: 255660, kind: "subtotal" }, { conta: "Despesas com pessoal", value: -121300 }, { conta: "Marketing", value: -38900 }, { conta: "Administrativo", value: -27400 }, { conta: "EBITDA", value: 68060, kind: "subtotal" }, { conta: "Depreciação", value: -9200 }, { conta: "Resultado financeiro", value: 3150 }, { conta: "Lucro líquido", value: 62010, kind: "total" }, ]; el.addEventListener("ab-point-click", (e) => abToast(`${e.detail.row.conta}: saldo de ${e.detail.step.before ?? "?"} para ${e.detail.step.after ?? "?"}`, { tone: "info" })); </script> Cascata com saldo negativo e valor ausenteSubtotais e totais podem ser negativos. Um valor ausente, não numérico ou infinito nunca vira zero: a linha fica sem barra (—), o saldo passa a ser desconhecido e as variações seguintes aparecem com ? até o próximo subtotal com valor. Zero continua sendo um valor.<ab-chart type="waterfall" x="etapa" heading="Caixa do projeto" description="Saldo inicial como primeiro subtotal" data='[{"etapa":"Saldo inicial","value":20000,"kind":"subtotal"},{"etapa":"Recebimentos","value":15000},{"etapa":"Fornecedores","value":-48000},{"etapa":"Saldo parcial","value":-13000,"kind":"subtotal"},{"etapa":"Impostos","value":null},{"etapa":"Aporte","value":30000},{"etapa":"Saldo conferido","value":12500,"kind":"subtotal"},{"etapa":"Ajuste","value":0},{"etapa":"Saldo final","value":12500,"kind":"total"}]'></ab-chart> Sparklines em KPIs e tabelasab-sparkline: tendência compacta sem eixos. values aceita array ou texto “3,5,4”. 10 meses 7 meses 7 dias <ab-grid cols="3"> <ab-stack gap="2"> <ab-kpi label="Receita (out)" value="142300" type="currency" delta="0.024"></ab-kpi> <ab-cluster gap="2"><ab-sparkline values="98,104,113,108,122,126,120,131,139,142" type="area" width="160"></ab-sparkline><ab-text type="secondary">10 meses</ab-text></ab-cluster> </ab-stack> <ab-stack gap="2"> <ab-kpi label="Inadimplência" value="0.043" type="percent" delta="0.008" invert-delta tone="danger"></ab-kpi> <ab-cluster gap="2"><ab-sparkline values="3.1,3.4,3.2,3.6,3.9,4.1,4.3" tone="danger" width="160"></ab-sparkline><ab-text type="secondary">7 meses</ab-text></ab-cluster> </ab-stack> <ab-stack gap="2"> <ab-kpi label="Pedidos/dia" value="38" delta="0.11" tone="ok"></ab-kpi> <ab-cluster gap="2"><ab-sparkline values="22,31,28,35,30,41,38" type="bar" tone="ok" width="160"></ab-sparkline><ab-text type="secondary">7 dias</ab-text></ab-cluster> </ab-stack> </ab-grid> <ab-table id="t-spark" heading="Vendas por loja" style="margin-top: var(--ab-space-4)"></ab-table> <script type="module"> const t = document.getElementById("t-spark"); const trend = (values, tone) => Object.assign(document.createElement("ab-sparkline"), { values, tone }); t.columns = [ { key: "loja", label: "Loja" }, { key: "total", label: "Total (out)", type: "currency" }, { key: "hist", label: "Últimos 6 meses", render: (r) => trend(r.hist, r.hist.at(-1) >= r.hist[0] ? "ok" : "danger") }, ]; t.rows = [ { loja: "Centro", total: 58200, hist: [41, 44, 47, 52, 55, 58] }, { loja: "Shopping Norte", total: 47100, hist: [52, 50, 49, 47, 48, 47] }, { loja: "Online", total: 37000, hist: [18, 22, 27, 29, 33, 37] }, ]; </script> Carregando, vazio e erroloading mostra um skeleton; sem linhas mostra ab-empty; error mostra a mensagem e dispara ab-retry. <ab-grid cols="3"> <ab-chart heading="Carregando" loading height="200"></ab-chart> <ab-chart id="c-vazio" heading="Sem dados" empty-text="Nenhuma venda no período" height="200"></ab-chart> <ab-chart id="c-erro" heading="Com erro" error="Não foi possível carregar as vendas." height="200"></ab-chart> </ab-grid> <script type="module"> const el = document.getElementById("c-erro"); el.addEventListener("ab-retry", () => { el.error = ""; el.loading = true; setTimeout(() => { el.data = [{ mes: "Ago", vendas: 31 }, { mes: "Set", vendas: 44 }, { mes: "Out", vendas: 39 }]; el.type = "line"; el.loading = false; }, 600); }); </script> APIAtributos, propriedades, métodos, eventos e slots de cada tag. <ab-chart>

Chart for dashboards: line, area, bar (vertical), bar-horizontal, pie and donut. Hand-rolled SVG, theme-aware.

Data comes straight from Python rows: chart.data = rows (array of objects), x="mes" names the category/date field, and chart.series = [{ key: "receita", label: "Receita" }, { key: "meta", label: "Meta", type: "line" }] names the numeric fields. series may also be a list of keys (["receita", "despesa"], or the attribute series="receita,despesa"); when omitted, every numeric field except x becomes a series. Pie/donut use x as the slice label and the first series as the slice value.

Values: numbers or numeric strings; null / missing values leave a gap (no bar, a break in the line) and don't count as zero. x values like "2026-03-01" or "2026-03-01T10:00" are dates: axis labels and the tooltip format them (months when every date is the 1st). Money and percents: value-type="currency" / "percent" (property valueType) formats the axis and the tooltip. Replace data to update (the bars/lines animate to the new values); abLoad(chart, () => get_series(period)) adds loading, error, retry and drops stale responses.

Several bar series are grouped side by side; add stacked to stack them, normalized for 100% stacked. Hover/focus shows a tooltip with every series for that x; arrow keys move between points and the value is announced. A hidden data table (and a "Ver dados" toggle when there is a heading) exposes the numbers to screen readers. Loading, empty and error states built in. Colors: series color (tone or CSS color) or the token palette --ab-chart-1..--ab-chart-8 (derived from --ab-accent and status tokens).

Waterfall (type="waterfall", e.g. an income statement / DRE): one row per bar, in the order given (never sorted, even with x-type="date"). x is the label, the first series is the value (default: the value field) and the row's kind field says what the value is:

  • "change" (default when kind is missing): signed movement; the bar goes from the running balance to balance + value (revenue up, expense down).
  • "subtotal" / "total": the reported balance at that row (may be negative); the bar goes from zero to the value, which becomes the running balance. It is drawn as given, never recalculated or added to the balance. The balance starts at zero; to start elsewhere, make the first row a subtotal ("Saldo inicial"). chart.data = [{ label: "Receita bruta", value: 1000, kind: "change" }, { label: "Deduções", value: -100 }, { label: "Receita líquida", value: 900, kind: "subtotal" }, { label: "Custos", value: -600 }, { label: "Resultado", value: 300, kind: "total" }]. A missing, non-numeric or non-finite value (or an unknown kind) is never read as zero: that row has no bar and is marked "Sem valor", and the balance becomes unknown, so the following changes have no bar ("Saldo anterior desconhecido") until a subtotal/total with a value. A zero is a valid value (a flat bar). Increases, decreases and subtotals/totals use --ab-ok, --ab-danger and a neutral tone, with a legend, signed value labels, a tooltip (movement, previous and ending balance) and a "Ver dados" table with the exact numbers. With many rows the plot scrolls horizontally; long labels wrap to two lines. The exported waterfallSteps(items) returns the same from/to the chart draws.
<ab-sparkline>Tiny inline trend (no axes) for KPI cards and table cells.