@waler/ui

Design system Vue 3 da WALER, sobre Reka UI e Tailwind CSS v4
2,928
Install
npm install @waler/ui
Latest Version:3.7.1
Vue:3
License:MIT
Last Updated:Sep 28, 2026
Links: npm
Maintainer: viniciusfgx

@waler/ui

Design system Vue 3 da WALER: 31 componentes acessíveis sobre Reka UI, estilizados com Tailwind CSS v4 e pintados pela paleta da marca.

ESM puro, tree-shakeable, tipado. Sem icon set, sem clsx, sem @vueuse/core. O culori só entra pelo subpath @waler/ui/brand, e o TipTap só pelo RichTextEditor: quem importa apenas o RichTextContent não leva editor nenhum no bundle, e o verify:fixture confere isso a cada build.

pnpm add @waler/ui

Requer vue@^3.5 e tailwindcss@^4.1 no projeto.


Setup

/* src/main.css */
@import "tailwindcss";
@import "@waler/ui/theme.css";
@source "../node_modules/@waler/ui/dist";

A linha @source é obrigatória. O Tailwind v4 ignora node_modules na detecção automática de sources. Sem ela os componentes renderizam sem estilo nenhum e sem erro nenhum — a falha é silenciosa, e é o erro de integração mais comum com esta lib.

Se estiver usando pnpm e os estilos não aparecerem, tente a forma que atravessa o store:

@source "../node_modules/.pnpm/**/@waler/ui/dist";

Como o Tailwind escaneia o dist por texto, o CSS gerado cobre todos os componentes do pacote, não só os que você importou. Na prática são utilities que a aplicação normalmente já produz.

Uso

<script setup lang="ts">
import { Button, Dialog, DialogTrigger, DialogContent, DialogTitle } from '@waler/ui'
</script>

<template>
  <Dialog>
    <DialogTrigger as-child>
      <Button>Abrir</Button>
    </DialogTrigger>
    <DialogContent>
      <DialogTitle>Confirmar</DialogTitle>
    </DialogContent>
  </Dialog>
</template>

Tooltip exige um TooltipProvider na raiz do app.

Componentes

Avatar Badge Breadcrumb Button
Calendar Card Checkbox DatePicker
Dialog Drawer FileUpload Group
Input Kbd Label LinkCard
MenuItem Popover Progress RadioGroup
RichTextContent RichTextEditor SectionHeader Select
Steps Surface Switch Textarea
Timeline Toaster Tooltip

Input, Textarea, Select, DatePicker, FileUpload e RichTextEditor são a camada de formulário: cada um recebe label, description, error e required e amarra sozinho o aria-describedby e o aria-invalid ao próprio controle. Não há componente de campo separado — o rótulo mora no controle porque é lá que o id nasce.

O Input cobre ainda prefixo e sufixo (props prefix/suffix ou slots de mesmo nome) e os tipos formatados currency, decimal e phone; qualquer outro type passa direto para o DOM.

Todo componente expõe as e asChild quando envolve um primitivo do Reka, e exporta o próprio tipo de props e de variantes.

Texto formatado

RichTextEditor edita e RichTextContent desenha o mesmo documento, o tipo RichTextDoc: o JSON do TipTap na forma canônica, sem atributo padrão e com o link só com href. Guarde o documento como sai do v-model. O renderizador não usa v-html e confere de novo o protocolo de cada link (http, https, mailto e tel); fora disso o link vira texto. O leitor desenha o corpo em 14px, peso 400, linha de 24px e 12px entre parágrafos; o editor escreve no text-body dos outros campos.

<RichTextEditor
  v-model="doc"
  label="Mensagem"
  placeholder="Escreva o comunicado"
  @slash="openInsertMenu"
/>
<RichTextContent :doc="doc" />

O slash sai quando "/" é digitado num parágrafo vazio do topo (dentro de lista ou citação, "/" é texto) e traz uma âncora que o PopoverAnchor aceita em reference. Listas aninham até três níveis, no Tab e no colar.

Superfícies

Três, e só três: branco sólido, vidro e gradiente. Surface é o primitivo que carrega as três, com o fallback opaco de backdrop-filter e o scrim do gradiente já embutidos.

<Surface variant="glass" radius="lg" elevation="card" class="p-6">
  conteúdo
</Surface>

Texto sobre gradiente herda a tinta certa sozinho, via data-wa-surface no elemento raiz da superfície.

O scrim melhora o contraste mas não garante: no fim do gradiente do banner o branco fica em 2.25:1, abaixo de AA. Mantenha texto no primeiro terço do eixo.

Customização

Sobrescreva os tokens semânticos depois de importar o tema. Eles têm prefixo --wa- porque :root é global e last-wins — um --primary sem prefixo colidiria em silêncio com o do seu app:

@import "@waler/ui/theme.css";

:root {
  --wa-primary: #1E3A2F;
  --wa-accent: #2C4A3F;
}

A utility continua sendo bg-primary. O prefixo fica só na camada de valor.

Para ajuste pontual todo componente aceita class, e o tailwind-merge resolve o conflito. Para trocar o elemento renderizado, use as ou asChild:

<Button as-child>
  <RouterLink to="/planos">Ver planos</RouterLink>
</Button>

Cor de tenant

O subpath @waler/ui/brand resolve o caso whitelabel: cada tenant define seis cores, e elas têm de ser recusadas antes do cadastro em vez de degradarem em runtime numa tela que ninguém está olhando.

É TypeScript puro — sem Vue e sem CSS — porque quem o consome é o servidor, e servidor não deve arrastar componente nenhum para validar seis hex.

import { checkBrand, brandToCss, BRAND_TOKENS } from '@waler/ui/brand'

const brand = { '--wa-ink': '#16211D', '--wa-deep': '#1E3A2F', /* ... */ }

const report = checkBrand(brand)
if (!report.ok) {
  // findings diz o par, a razão medida, o mínimo e quais dos seis a causaram
  throw new Error(report.findings.map(f => `${f.pair}: ${f.ratio}`).join('\n'))
}

const css = brandToCss(brand) // bloco :root pronto para servir

BRAND_TOKENS é a lista fechada — --wa-ink, --wa-deep, --wa-soft, --wa-g1, --wa-g2, --wa-g3. Chave fora dela é recusada, não descartada: um tenant cujo token sumiu em silêncio não tem como distinguir isso de um token que a lib ignora.

O valor tem de ser hex sem alpha. #2C4A3F00 é transparente e apagaria o anel de foco do produto inteiro passando por qualquer validação que só olhe formato.

O bloco só repinta aplicado em :root. A camada de valor é global e last-wins por construção — em um nó interno ele não tem efeito. Sirva o CSS por uma tag <link> no index.html, não por JavaScript depois do mount: é o que faz a cor chegar antes do primeiro paint, sem flash da cor errada.

report.bands traz, por par do banner, o maior prefixo [0, t] do gradiente em que o texto ainda passa AA — as bandas do manifesto foram medidas com o gradiente da Waler, e o de cada tenant é outro.

Cor primária não é o verde

--wa-primary é o ink #16211D, não o verde da marca. No handoff da Waler o verde é acento, não ação. Componente que assumir "primário = cor de marca" sai errado.

Licença

MIT © WALER

Related Packages

@hina-ui/vue

The design system behind Hikarinagi: Vue 3 components built on Tailwind CSS v4.

4,608 10
@getweini/vue

Shared behavior and styles for editable Weini UI components in Vue 3 and Nuxt.

659
@dillingerstaffing/strand-vue

Strand UI - Vue 3 component library built on the Strand Design Language

15,710 0
@revenexx/studio

Revenexx Studio — Vue 3 + Tailwind v4 component library (shadcn-vue based).

5,025
@treeui/vue

Vue 3 component package for TreeUI.

7,405 0

Version History

Version Released License
3.7.1 MIT
3.7.0 MIT
3.6.2 MIT
3.6.1 MIT
3.6.0 MIT