Componentes
Exemplos shadcn-style. Copia o código, cola no teu projecto, adapta. Os componentes vivem em cada projecto, não numa lib partilhada.
Input
<input type="text" placeholder="Escreve aqui"
class="w-full px-3 py-2 rounded-md border border-slate-300
bg-white text-slate-800 text-base
placeholder:text-slate-400
focus:outline-none focus:ring-2 focus:ring-rose-500 focus:border-rose-500
disabled:bg-slate-50 disabled:cursor-not-allowed" /> Card
Título do card
Descrição curta a explicar o conteúdo. Slate-600 para secundário.
<div class="bg-white rounded-lg border border-slate-200 shadow-sm overflow-hidden">
<div class="p-6">
<h3 class="text-lg font-semibold text-slate-900 mb-1">Título do card</h3>
<p class="text-sm text-slate-600">Descrição curta. Slate-600 para secundário.</p>
</div>
<div class="px-6 py-4 bg-slate-50 border-t border-slate-200 flex justify-end gap-2">
<button class="...">Acção</button>
</div>
</div> Badge
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-emerald-100 text-emerald-800">
Activo
</span> Alert
<div class="flex gap-3 p-4 rounded-md bg-rose-50 border border-rose-200 text-rose-900">
<svg class="w-5 h-5 flex-shrink-0" ...><!-- lucide alert-circle --></svg>
<div>
<div class="font-semibold text-sm">Algo correu mal</div>
<div class="text-sm text-rose-800">Explica o que aconteceu e o que fazer a seguir.</div>
</div>
</div> Checkbox
<label class="flex items-start gap-2 cursor-pointer">
<input type="checkbox" class="mt-0.5 w-4 h-4 rounded border-slate-300 text-rose-600 focus:ring-rose-500" />
<span class="text-sm text-slate-700">Aceito os termos</span>
</label> Radio
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="opcao" class="w-4 h-4 border-slate-300 text-rose-600 focus:ring-rose-500" />
<span class="text-sm text-slate-700">Opção A</span>
</label> Select
<select class="w-full px-3 py-2 pr-8 rounded-md border border-slate-300
bg-white text-slate-800 text-base
focus:outline-none focus:ring-2 focus:ring-rose-500 focus:border-rose-500">
<option>Escolhe uma opção</option>
<option>Opção A</option>
</select> Tabs
<div class="border-b border-slate-200">
<nav class="flex gap-6 -mb-px">
<button class="px-1 py-3 text-sm font-medium border-b-2 border-rose-600 text-rose-600">Activo</button>
<button class="px-1 py-3 text-sm font-medium border-b-2 border-transparent text-slate-600 hover:text-slate-900 hover:border-slate-300">Inactivo</button>
</nav>
</div> Tooltip
Apenas CSS. Para tooltips com trigger em click / keyboard, usa Radix / Headless UI.
<div class="relative group inline-block">
<button class="text-slate-600 hover:text-slate-900">?</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 rounded
bg-slate-800 text-white text-xs whitespace-nowrap opacity-0 group-hover:opacity-100
pointer-events-none transition-opacity duration-fast z-tooltip">
Texto do tooltip
</div>
</div> Modal (Dialog)
Markup de referência. Para acessibilidade completa (focus trap, ESC, aria), usa <dialog> nativo ou Radix Dialog / Headless UI.
Apagar factura
Esta acção não pode ser desfeita. Queres continuar?
<!-- Backdrop -->
<div class="fixed inset-0 bg-slate-900/50 z-overlay"></div>
<!-- Modal -->
<div class="fixed inset-0 z-modal flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-lg max-w-md w-full">
<div class="p-6">
<h2 class="text-lg font-semibold text-slate-900 mb-2">Confirmar</h2>
<p class="text-sm text-slate-600">Esta acção não pode ser desfeita.</p>
</div>
<div class="px-6 py-4 bg-slate-50 border-t border-slate-200 flex justify-end gap-2">
<button class="px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 rounded-md">Cancelar</button>
<button class="px-4 py-2 text-sm bg-rose-600 text-white rounded-md hover:bg-rose-700">Confirmar</button>
</div>
</div>
</div> Form pattern
Composição canónica: label em cima, asterisco para required, helper em baixo, erro em vermelho com aria-describedby.
<form class="space-y-5 max-w-md">
<div>
<label for="name" class="block text-sm font-medium text-slate-700 mb-1">
Nome <span class="text-rose-600">*</span>
</label>
<input id="name" required
class="w-full px-3 py-2 rounded-md border border-slate-300 text-base
focus:outline-none focus:ring-2 focus:ring-rose-500 focus:border-rose-500" />
<p class="mt-1 text-xs text-slate-500">Como aparece no recibo.</p>
</div>
<div>
<label for="email" class="block text-sm font-medium text-slate-700 mb-1">Email</label>
<input id="email" type="email" aria-invalid="true" aria-describedby="email-err"
class="w-full px-3 py-2 rounded-md border border-red-300 text-base
focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-red-500" />
<p id="email-err" class="mt-1 text-xs text-red-600">Email inválido</p>
</div>
<button type="submit" class="px-4 py-2 bg-rose-600 text-white rounded-md hover:bg-rose-700">
Submeter
</button>
</form> Empty state
Para listas/tabelas vazias, 404 de conteúdo, ou search sem resultados. Sempre com CTA primária + descrição curta.
Sem facturas ainda
Quando criares a tua primeira factura, aparece aqui. Leva menos de um minuto.
<div class="max-w-md mx-auto text-center py-8">
<div class="w-14 h-14 mx-auto mb-4 rounded-full bg-rose-50 text-rose-600 flex items-center justify-center"><!-- icon --></div>
<h3 class="text-lg font-semibold text-slate-900 mb-1">Título</h3>
<p class="text-sm text-slate-600 mb-5">Descrição curta a orientar o próximo passo.</p>
<button class="inline-flex items-center gap-2 px-4 py-2 bg-rose-600 text-white font-medium text-sm rounded-md hover:bg-rose-700">CTA primária</button>
</div> Semantic colors
Para estados (success/warning/error/info). Cada um tem 3 tons: subtle (bg suave), base (sólido/ícone), text (texto sobre subtle).
<span class="bg-success-subtle text-success-text px-2 py-0.5 rounded-full">Pago</span>
<span class="bg-warning-subtle text-warning-text px-2 py-0.5 rounded-full">A expirar</span>
<span class="bg-error-subtle text-error-text px-2 py-0.5 rounded-full">Em atraso</span>
<span class="bg-info-subtle text-info-text px-2 py-0.5 rounded-full">Rascunho</span>