Modules
Componentes Phoenix LiveView com a identidade visual do Frappe UI.
Aritmética de calendário pura usada pelo DatePicker (semana começando no domingo).
Grade horizontal, rótulos do eixo Y e rótulos de categorias (SVG).
Gráfico de barras agrupadas (SVG no servidor, cores do tema, estados loading/empty/error).
Geometria compartilhada pelos gráficos de eixos (barras, linhas, áreas): área de plotagem, escala Y, posições X por categoria e grade. Funções puras.
Cartão que enquadra um gráfico: título, subtítulo, ações e o gráfico no corpo.
Gráfico de rosca (SVG no servidor). data é [%{label, value}]; valores ≤ 0 são ignorados.
Moldura comum dos gráficos com os estados :loading, :empty e :error
(visualmente distintos); no estado :ready mostra o gráfico.
Legenda: marcador colorido + nome. items é [%{label, color}] ou vem das séries.
Gráficos de linha e de área (SVG no servidor), com pontos, dicas e estados.
Mapas de calor: matriz linhas × colunas e calendário (estilo contribuições).
Cartão de indicador: rótulo, valor grande e variação percentual opcional (verde sobe, vermelho cai).
Gráficos de proporção: barra percentual empilhada e funil.
Diagrama de Sankey: links é [%{source, target, value}]; os nós e as colunas
(profundidade) são derivados dos links. Ciclos não são suportados e falham alto.
Matemática pura dos gráficos: escalas lineares, "números bonitos" para eixos, paleta de cores do tema e formatação numérica estável para SVG.
Gráfico de dispersão: séries de pontos {x, y} em eixos numéricos.
Dica de um ponto de dado, desenhada em SVG: aparece quando o pai tem a classe
group e está em hover/foco (CSS puro). Complementa o <title> nativo.
Catálogo de componentes: módulo, função, categoria e título. Fonte única usada
por use FrappeUIPhx (imports), pelo mostruário (navegação e especificação)
e pelo teste que garante que nenhum componente público fica sem catálogo.
Seções recolhíveis sobre <details> nativo (teclado e leitores de tela de graça).
Por padrão só uma seção fica aberta por vez (atributo name do HTML, agrupado
pelo id); com multiple, qualquer combinação. open no item abre desde o servidor.
Único lugar onde se decide "link ou botão" para um item de ação (navegação, abas,
menus). NavItem, MenuItem e Tabs renderizam por aqui.
Alerta com título, descrição e ação opcional. Temas gray | blue | green | amber | red.
Posicionamento de flutuantes por CSS Anchor Positioning (sem JS).
Avatar com imagem ou iniciais do label. Formas circle | square.
Selo de status: temas gray | blue | green | amber | red | violet, variantes solid | subtle | outline | ghost.
Folha inferior (mobile): um Dialog com placement: :bottom; abre/fecha com show_dialog/1 e hide_dialog/1.
Trilha de navegação; o último item é a página atual (aria-current="page").
Botão do Frappe UI: variantes solid | subtle | outline | ghost, temas
gray | blue | green | red, tamanhos xs | sm | md | lg.
Calendário mensal com eventos (visão de agenda). O mês exibido e o dia
selecionado são assigns do servidor; navegar e escolher são eventos
(on_prev, on_next, on_today, on_select com %{"date" => "2026-03-09"}).
Caixa de seleção com rótulo opcional. Emite o hidden com "false" como o
core_components do Phoenix, para que o desmarcado chegue ao servidor.
Editor de código: <textarea> monoespaçado com numeração de linhas, tecla Tab
que indenta e rótulo da linguagem. É um campo de formulário comum (name,
phx-change, field).
Seleção única com busca. Aceita field (Phoenix/Ecto); a escolha é um radio nativo do formulário.
Paleta de comandos (Ctrl/⌘ + K): busca, grupos e navegação por teclado. Abre
como o Dialog (mesmo hook nativo: foco preso, Esc, backdrop) e o hook próprio só
filtra a lista, move a seleção com ↑/↓ e aciona o item com Enter. O que cada
item faz é decidido pelo servidor (click) ou por navegação.
Menu de contexto (botão direito) sobre uma região. O hook colocated só
posiciona e abre o popover no cursor; o que cada item faz é decidido pelo
servidor (click) ou por navegação.
Seletor de data com calendário (LiveComponent: o mês exibido é estado próprio).
Casca de página para desktop: :sidebar à esquerda e o conteúdo rolável à direita.
Diálogo modal sobre <dialog> nativo (foco preso, Esc e backdrop pelo navegador).
Avatar gerado pelo DiceBear a partir de uma seed:
a mesma style + seed + options dá sempre o mesmo desenho. Mesmos tamanhos e
formas do FrappeUIPhx.Components.Avatar, de quem herda o visual (composição).
Linha divisória horizontal ou vertical.
Menu suspenso sem JavaScript: Popover API (light-dismiss e Esc nativos) + CSS Anchor Positioning (inverte para cima quando falta espaço).
Campo de duração: mostra 1h 30m quando o valor são segundos e aceita o que
FrappeUIPhx.Duration entende. Combine com FrappeUIPhx.Ecto.Duration no schema.
Editor de texto rico (negrito, itálico, título, listas, código, link) que
guarda Markdown num <input type=hidden>, então participa do formulário e
do phx-change como qualquer campo.
Mensagem(ns) de erro. Aceita uma string, uma lista de strings ou nil (não renderiza nada).
Área de envio sobre allow_upload/3 do LiveView: arrastar-e-soltar, lista de
arquivos com progresso, cancelamento e erros em português. Quem decide o que
fazer com o arquivo (consume_uploaded_entries/3) é o servidor.
Rótulo + controle + descrição + erros, escolhendo o controle por type.
Rótulo de campo; required acrescenta o asterisco e deve espelhar o required do controle.
Cartão que aparece em hover ou foco do gatilho, com pequeno atraso (CSS puro).
Ícone Lucide por nome, renderizado como <svg> inline (veja FrappeUIPhx.Icons).
Classes compartilhadas por TextInput, Textarea e Select (função pura).
Linha de lista com ícone, título, subtítulo e ações; navegável por navigate/patch/href.
Atalho de teclado: uma <kbd> por tecla (keys={["Ctrl", "K"]}).
Diálogo com a lista de atalhos: shortcuts={[%{label: "Buscar", keys: ["Ctrl", "K"]}]}.
Tabela de objetos: colunas por slot (:col), ações por linha (:action),
seleção por checkbox e reordenação por arrastar-e-soltar. O estado
(selected, ordem de rows) vive no servidor; cada interação é um evento.
Lista agrupada e recolhível: group_by (função linha → rótulo) forma os
grupos; os recolhidos (collapsed) são um assign do servidor e o cabeçalho de
cada grupo dispara on_toggle com %{"group" => rótulo}. Colunas por slot,
como em list. Experimental.
Texto de carregamento com spinner.
Item de menu (Dropdown, ContextMenu): navega (navigate/patch/href) ou
dispara evento (click) e fecha o popover pai. Tema :gray ou :red.
Casca para telas estreitas: cabeçalho, conteúdo rolável e navegação inferior (:nav).
Seleção múltipla com busca e selos. Envia name[] (mais um "" para permitir
esvaziar); combine com FrappeUIPhx.Ecto.StringList, que descarta o vazio.
Item de navegação com ícone e rótulo (Tabs, TabButtons, Sidebar, SidebarRail,
SettingsDialog, MobileShell). Navega por patch/navigate/href ou dispara click
(evento ou %JS{}) — a decisão "link ou botão" é de FrappeUIPhx.Components.Action.
O estado ativo é decidido por quem chama (assign do servidor).
Base de Combobox e MultiSelect: gatilho + painel (Popover API) com busca e
opções que são <input type=radio|checkbox> nativos — então a seleção já é
valor de formulário e dispara o phx-change sem nenhum JS de negócio.
Cabeçalho de página: breadcrumbs opcionais, título e ações.
Campo de senha com botão de mostrar/ocultar. A alternância é só visual
(JS.toggle_attribute no type); o valor nunca é reenviado ao HTML.
Código de verificação / PIN: uma caixa por caractere, com avanço automático,
Backspace que volta, setas, colar o código inteiro e preenchimento por SMS
(autocomplete="one-time-code").
Painel flutuante com conteúdo livre: Popover API + CSS Anchor Positioning (sem JS).
Barra de progresso (value de 0 a 100) com rótulo e valor opcionais.
Grupo de opções exclusivas (<input type=radio> nativos); options como em Select.
Avaliação em estrelas (1..max). Cada estrela é um <input type=radio>, então
funciona em formulários e phx-change; o preenchimento vem do valor no servidor.
Combine com FrappeUIPhx.Ecto.Rating no schema.
Confirmação visual transitória ("Salvo") sem useEffect/timer: o servidor
devolve um valor que muda a cada sucesso (at, ex. um timestamp) e o :key
remonta o elemento, reiniciando a animação CSS que termina em opacidade 0.
Região rolável com barras finas e discretas (CSS nativo); focável para rolagem por teclado.
Seleção nativa. options aceita lista de strings, {rótulo, valor} ou
keyword; prompt adiciona uma primeira opção vazia.
Diálogo de configurações: navegação lateral por seções (:tab) e o conteúdo
da seção ativa. A seção ativa (active) é um assign do servidor.
Barra lateral de navegação. O item ativo (active) e o recolhimento
(collapsed) são assigns do servidor; recolhida, só os ícones aparecem.
Trilho estreito só com ícones (rótulo acessível e title); ativo por assign.
Bloco animado para estados de carregamento; dimensione por class (ex.: h-4 w-32).
Controle deslizante (<input type=range>); min <= value <= max é pré-condição.
Lista reordenável por arrastar-e-soltar (HTML5 nativo).
Indicador de carregamento; herda a cor do texto.
Indicador de etapas. A etapa atual (current, índice a partir de 0; igual ao
número de etapas = tudo concluído) é um assign do servidor, e o estado de cada
etapa (:done | :current | :upcoming) é derivado dele. Com on_go, as etapas
concluídas viram botões que voltam para elas (%{"step" => índice}). Experimental.
Interruptor. É um checkbox nativo (peer), então funciona em formulários sem JS.
Abas em formato de botões segmentados; a ativa é um assign do servidor (active).
Abas cujo item ativo é decidido por um assign (active), nunca por estado do
cliente. Cada :tab navega (patch/navigate) ou dispara evento (click).
O conteúdo do painel ativo vai no inner_block.
Campo de uma linha. Aceita field={@form[:campo]} (Phoenix/Ecto) ou
name/value avulsos. Restrições (required, maxlength, min, pattern,
placeholder...) vão direto ao <input>; o servidor continua sendo a
barreira real (o required aqui espelha a regra do changeset).
Campo de várias linhas; mesmo contrato de FrappeUIPhx.Components.TextInput.
Alternador claro/escuro. Cada opção é um link ?theme=… tratado por
FrappeUIPhx.Plug.Theme, que persiste a escolha em sessão e cookie; o tema
atual (current) vem do servidor (@frappe_ui_theme). Experimental.
Seletor de horário (<input type=time> nativo, com o teclado/relógio do dispositivo); step em segundos.
Container de toasts: mostra mensagens de flash (renderizadas no servidor) e
as enviadas por FrappeUIPhx.Toast.put_toast/4 (via hook colocated).
Dica visual sem JavaScript: aparece em hover e em foco do conteúdo envolvido.
Árvore de nós %{id, label, icon, children}. Expansão (expanded) e seleção
(selected) são assigns do servidor; cada clique é um evento
(on_toggle/on_select com %{"id" => id}). Com on_drop, os nós de um
mesmo nível podem ser reordenados por arrastar-e-soltar.
Resolve e valida o módulo configurado para um ponto de extensão
(IconResolver, ToastSink, ErrorTranslator, Translator). Um módulo inexistente
ou sem o callback falha na primeira chamada com mensagem clara, em vez de um
UndefinedFunctionError no meio da renderização.
Design by Contract para os componentes.
URL da API HTTP do DiceBear (avatares gerados a partir de uma seed).
Duração em segundos ⇄ texto digitado (1h 30m, 90m, 45s, 1:30, 1:30:15, 5400).
Ecto.Type para o campo Duration do Frappe UI: guarda segundos (inteiro
não negativo) e aceita no cast o que um usuário digita
Opções de Select/Combobox a partir de um campo Ecto.Enum, para que a
lista da tela e o domínio nunca divirjam
Ecto.ParameterizedType para avaliação em estrelas: inteiro de 0 a max
(padrão 5). Aceita string vindo do formulário.
Ecto.Type para listas de textos (tags, MultiSelect): persiste como
{:array, :string} e aceita no cast uma lista ou uma string separada por
vírgula. Descarta vazios e repetidos, preservando a ordem.
Contrato de tradução de erros de changeset {mensagem, opções}.
Tradutor padrão de erros de changeset: interpola %{chave} da mensagem com as opções
do erro, sem depender de Gettext nem do próprio Ecto.
Costura entre Phoenix.HTML.FormField (changeset/Ecto) e os controles.
Fundamentos visuais do Frappe UI (cores, tipografia, raios, sombras, anel de
foco e cores de gráficos) como dados, lidos de priv/foundations.json, que
mix tokens.sync gera da mesma fonte dos tokens do CSS. Alimenta as páginas de
Fundamentos do mostruário e serve de referência em código (ex.: listar tokens).
Contrato de como o nome de ícone usado no projeto vira um nome do catálogo Lucide
(FrappeUIPhx.Icons). Serve para apelidos do projeto ("salvar" -> "save")
Resolvedor padrão: o nome já é do catálogo Lucide e passa direto.
Catálogo de ícones Lucide: nomes, tags, aliases (nomes antigos) e busca.
Um componente LiveView por ícone Lucide (atalhos de FrappeUIPhx.Icons.Svg).
Renderiza um ícone do catálogo como <svg> inline, com as propriedades do Lucide.
Edições idempotentes nos arquivos do app host (CSS, JS, mix.exs), em funções
puras conteúdo -> {:ok, novo, mudou?} | {:error, motivo}. A task
mix frappe_ui_phx.install só lê/escreve arquivos em volta delas.
on_mount que copia o tema resolvido por FrappeUIPhx.Plug.Theme (guardado
na sessão) para socket.assigns.frappe_ui_theme, mantendo o estado autoritativo
no servidor também dentro do LiveView.
Subconjunto seguro de Markdown usado pelo Editor: ## título, listas
(- e 1.), parágrafos, **negrito**, _itálico_, `código` e
[texto](https://url).
Serve frappe_ui.css (tokens) e frappe_ui_icons.css da própria dependência em /frappe_ui/,
para quem prefere <link> a @import no app.css.
Resolve o tema ("light" ou "dark") e o expõe em conn.assigns.frappe_ui_theme
para o layout raiz: <html data-theme={@frappe_ui_theme}>.
Textos fixos da biblioteca (rótulos de acessibilidade, mensagens auxiliares), em português por padrão, sobrescrevíveis sem copiar componentes.
API de servidor para toasts (substitui toast() do Frappe UI).
Contrato de como um toast chega ao cliente. A implementação padrão
(FrappeUIPhx.ToastSink.PushEvent) usa push_event/3; troque via config
Entrega o toast ao hook .Toast pelo evento frappe-ui:toast.
Contrato para traduzir os textos fixos da biblioteca.
Mix Tasks
Configura o app Phoenix atual para usar a biblioteca. Idempotente: pode rodar quantas vezes quiser.