UaiUI
Sistema de design com bordas retas, sombras duras e temas por tokens. Cada componente tem preview, código e requisitos de acessibilidade.
Toast
Aviso curto que aparece depois de uma ação ou de um evento e some sozinho: salvou, falhou, nova versão. Não bloqueia a tela.
Cadastro salvo
<div aria-live="polite" aria-atomic="false">
<div class="uai-toast" data-tone="success">
<div class="uai-toast__bar"></div>
<div class="uai-toast__body">
<svg class="uai-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M5 12.5l4.5 4.5L19 7"/></svg>
<div class="uai-toast__content">
<p class="uai-toast__title">Cadastro salvo</p>
<p class="uai-toast__message">Seus dados foram atualizados.</p>
<button class="uai-button" data-type="outlined">Desfazer</button>
</div>
<button class="uai-button uai-icon-button" data-type="ghost" data-toast-close aria-label="Fechar aviso"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
</div>
</div>Use Mostrar aviso: o aviso entra na região viva da página e o leitor de tela o anuncia. Feche no X, em Desfazer ou com Esc: o foco volta ao botão.
Instalação
- Importe
uaiui-ds/tokens-wrapper.csse coloque os componentes dentro do wrapper.uaiui(veja Tokens e temas), e o CSS do button (uaiui-ds/components/button.css), usado na ação e no fechar. - Importe
uaiui-ds/components/toast.css. O código abaixo é o conteúdo desse arquivo.
/* toast: tone (neutral | success | warning | error | info). Barra lateral, ícone, título, mensagem, ação e fechar. */
.uai-toast {
--_bg: var(--toast-neutral-background); --_border: var(--toast-neutral-border);
--_bar: var(--toast-neutral-bar); --_icon: var(--toast-neutral-icon);
display: flex; box-sizing: border-box; width: var(--toast-width); max-width: 100%;
background: var(--_bg); color: var(--toast-txtOn);
border: var(--border-width-default) solid var(--_border); border-radius: var(--radius-control);
box-shadow: var(--shadow-offset-layout) var(--shadow-offset-layout) 0 0 var(--toast-shadow-color);
overflow: hidden;
}
.uai-toast[data-tone="success"] { --_bg: var(--toast-success-background); --_border: var(--toast-success-border); --_bar: var(--toast-success-bar); --_icon: var(--toast-success-icon); }
.uai-toast[data-tone="warning"] { --_bg: var(--toast-warning-background); --_border: var(--toast-warning-border); --_bar: var(--toast-warning-bar); --_icon: var(--toast-warning-icon); }
.uai-toast[data-tone="error"] { --_bg: var(--toast-error-background); --_border: var(--toast-error-border); --_bar: var(--toast-error-bar); --_icon: var(--toast-error-icon); }
.uai-toast[data-tone="info"] { --_bg: var(--toast-info-background); --_border: var(--toast-info-border); --_bar: var(--toast-info-bar); --_icon: var(--toast-info-icon); }
.uai-toast__bar { flex: none; width: var(--toast-bar-width); background: var(--_bar); }
.uai-toast__body { flex: 1; display: flex; align-items: flex-start; gap: var(--toast-gap); padding: var(--toast-padding); min-width: 0; }
.uai-toast__icon { flex: none; width: var(--toast-icon-size); height: var(--toast-icon-size); fill: none; stroke: var(--_icon); stroke-width: var(--toast-icon-weight); stroke-linecap: square; stroke-linejoin: miter; }
.uai-toast__content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--toast-content-gap); align-items: flex-start; }
.uai-toast__title { margin: 0; font: var(--font-weight-label) var(--toast-font-size)/var(--toast-line-height) var(--font-family-label), monospace; text-transform: var(--font-transform-label); }
.uai-toast__message { margin: 0; font: 400 var(--toast-font-size)/var(--toast-line-height) var(--font-family-body), sans-serif; }
.uai-toast__content .uai-button { margin-top: var(--size-scale-base); }Uso
<div class="uai-toast" data-tone="error">
<div class="uai-toast__bar"></div>
<div class="uai-toast__body">
<svg class="uai-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M4 4h16v16H4z"/><path d="M9 9l6 6M15 9l-6 6"/></svg>
<div class="uai-toast__content">
<p class="uai-toast__title">Falha no envio</p>
<p class="uai-toast__message">Não foi possível enviar o arquivo. Tente de novo.</p>
<button class="uai-button" data-type="outlined">Tentar de novo</button>
</div>
<button class="uai-button uai-icon-button" data-type="ghost" data-toast-close aria-label="Fechar aviso"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
</div>Quando usar. Para confirmar o resultado de uma ação ("Cadastro salvo") ou avisar de um evento que não pede resposta imediata, com uma ação opcional ligada ao aviso, como Desfazer ou Tentar de novo.
Quando não usar. Erro de um campo: use o estado error do próprio campo. Mensagem que precisa ficar na página: deixe o texto visível. Decisão que bloqueia a tela: use modal. Dica no hover: tooltip; conteúdo ao clique: popover.
Tons
Link copiado
Cadastro salvo
Sessão prestes a expirar
Falha no envio
Nova versão disponível
<div class="uai-toast" data-tone="neutral">
<div class="uai-toast__bar"></div>
<div class="uai-toast__body">
<svg class="uai-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M4 5h16v11h-9l-5 4v-4H4z"/></svg>
<div class="uai-toast__content">
<p class="uai-toast__title">Link copiado</p>
<p class="uai-toast__message">O endereço está na área de transferência.</p>
</div>
<button class="uai-button uai-icon-button" data-type="ghost" data-toast-close aria-label="Fechar aviso"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
</div>
<div class="uai-toast" data-tone="success">
<div class="uai-toast__bar"></div>
<div class="uai-toast__body">
<svg class="uai-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M5 12.5l4.5 4.5L19 7"/></svg>
<div class="uai-toast__content">
<p class="uai-toast__title">Cadastro salvo</p>
<p class="uai-toast__message">Seus dados foram atualizados.</p>
</div>
<button class="uai-button uai-icon-button" data-type="ghost" data-toast-close aria-label="Fechar aviso"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
</div>
<div class="uai-toast" data-tone="warning">
<div class="uai-toast__bar"></div>
<div class="uai-toast__body">
<svg class="uai-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M12 4L3 20h18z"/><path d="M12 10v5M12 17.5v.5"/></svg>
<div class="uai-toast__content">
<p class="uai-toast__title">Sessão prestes a expirar</p>
<p class="uai-toast__message">Você será desconectado em 2 minutos.</p>
</div>
<button class="uai-button uai-icon-button" data-type="ghost" data-toast-close aria-label="Fechar aviso"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
</div>
<div class="uai-toast" data-tone="error">
<div class="uai-toast__bar"></div>
<div class="uai-toast__body">
<svg class="uai-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M4 4h16v16H4z"/><path d="M9 9l6 6M15 9l-6 6"/></svg>
<div class="uai-toast__content">
<p class="uai-toast__title">Falha no envio</p>
<p class="uai-toast__message">Não foi possível enviar o arquivo.</p>
</div>
<button class="uai-button uai-icon-button" data-type="ghost" data-toast-close aria-label="Fechar aviso"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
</div>
<div class="uai-toast" data-tone="info">
<div class="uai-toast__bar"></div>
<div class="uai-toast__body">
<svg class="uai-toast__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7.5v.5"/></svg>
<div class="uai-toast__content">
<p class="uai-toast__title">Nova versão disponível</p>
<p class="uai-toast__message">Recarregue a página para usar a versão mais recente.</p>
</div>
<button class="uai-button uai-icon-button" data-type="ghost" data-toast-close aria-label="Fechar aviso"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
</div>Cada tom usa o fundo mais claro da família (nível 20), borda e barra lateral na cor dela, e um ícone próprio: balão (neutral), check (success), triângulo (warning), quadrado com X (error) e círculo com i (info). Nestes exemplos, o X esconde o aviso e ele volta depois de 2 segundos.
Anatomia
| Prop | Valores | Padrão | Descrição |
|---|---|---|---|
data-tone | neutral | success | warning | error | info | neutral | Define cor e ícone. Cada tom tem um desenho próprio; a cor nunca é o único sinal. |
uai-toast__title | texto | — | Título de 1 a 4 palavras. Sempre presente. |
uai-toast__message | texto | — | Mensagem em uma ou duas linhas. Sempre presente. |
ação | button outlined | ausente | Opcional, com rótulo de verbo: Desfazer, Tentar de novo. |
data-toast-close | icon button ghost | — | Botão de fechar, sempre presente. aria-label="Fechar aviso". |
região viva | aria-live="polite" | role="alert" | polite | Fica na página, fora do toast. polite para neutral, success e info; role="alert" para warning e error. O toast não leva role. |
A tabela rola na horizontal; a região recebe foco pelo teclado.
Acessibilidade
Mantenha uma região viva na página desde o início, para o leitor de tela anunciar o aviso quando ele entrar:
<!-- Regiões de aviso: ficam na página desde o início, mesmo vazias -->
<div aria-live="polite" aria-atomic="false">
<!-- insira aqui os toasts neutral, success e info -->
</div>
<div role="alert">
<!-- insira aqui os toasts warning e error -->
</div>- Neutral, success e info entram na região
aria-live="polite"; warning e error, na regiãorole="alert". O toast em si não levarole: o anúncio vem da região, uma vez só. - Não rouba o foco. A ação precisa ser alcançável pelo teclado.
- Some sozinho depois de pelo menos 5 s (mais tempo para mensagem longa). Não fecha enquanto o foco ou o ponteiro estiverem sobre ele. Esc fecha o aviso com foco.
- Com ação, warning ou error, só fecha quando a pessoa fechar.
- Sempre dá para fechar pelo botão. Nunca use o toast como único lugar de uma informação essencial ou de um erro.
- Posição: canto inferior, no fim da tela no Desktop; no Mobile, largura total com margem de 16 px. Não cobre a ação principal da página. Vários avisos empilham com 16 px, o mais novo por último.
- Entrada e saída de 100 ms; sem transição com redução de movimento.
Contraste
Texto acima de 16:1 em todos os tons, no Claro e no Escuro. Ícone e borda acima de 3:1. A barra lateral do warning no Claro tem 1,8:1, mas é reforço: o ícone e a borda já carregam o sinal.