Skip to content

Componentes UI

Padrões visuais dos componentes do design system. Cada componente especifica as classes Tailwind que definem sua aparência, os estados obrigatórios e as restrições de uso.

Os componentes são implementados em web/src/app/shared/components/. Esta página define o contrato visual — o que deve ser verdadeiro para qualquer implementação.

Botões

Hierarquia

Use apenas um botão Primary por tela ou seção. O restante deve ser Secondary, Ghost ou Danger.

VarianteQuando usar
PrimaryAção principal da tela (ex.: "Salvar", "Criar Agendamento")
SecondaryAção alternativa que não é destrutiva (ex.: "Exportar", "Visualizar")
GhostAções terciárias, navegação, cancelar dentro de formulários
DangerAções destrutivas irreversíveis (ex.: "Excluir", "Cancelar Agendamento")
LinkNavegação inline em parágrafos de texto

Primary

html
<button class="
  inline-flex items-center gap-2
  px-4 py-2 rounded-lg
  bg-brand-600 hover:bg-brand-700 active:bg-brand-800
  text-white text-sm font-medium
  shadow-sm
  transition-colors duration-150
  focus-visible:outline-none focus-visible:ring-2
  focus-visible:ring-brand-600 focus-visible:ring-offset-2
  disabled:opacity-50 disabled:pointer-events-none
">
  <lucide-icon name="plus" [size]="16" aria-hidden="true" />
  Novo Agendamento
</button>

Secondary

html
<button class="
  inline-flex items-center gap-2
  px-4 py-2 rounded-lg
  bg-white hover:bg-neutral-50 active:bg-neutral-100
  border border-neutral-200 hover:border-neutral-300
  text-neutral-700 text-sm font-medium
  shadow-sm
  transition-colors duration-150
  focus-visible:outline-none focus-visible:ring-2
  focus-visible:ring-brand-600 focus-visible:ring-offset-2
  disabled:opacity-50 disabled:pointer-events-none
">
  Exportar
</button>

Ghost

html
<button class="
  inline-flex items-center gap-2
  px-4 py-2 rounded-lg
  text-neutral-600 hover:text-neutral-900
  hover:bg-neutral-100 active:bg-neutral-200
  text-sm font-medium
  transition-colors duration-150
  focus-visible:outline-none focus-visible:ring-2
  focus-visible:ring-brand-600 focus-visible:ring-offset-2
  disabled:opacity-40 disabled:pointer-events-none
">
  Cancelar
</button>

Danger

html
<button class="
  inline-flex items-center gap-2
  px-4 py-2 rounded-lg
  bg-error-600 hover:bg-error-700 active:bg-error-800
  text-white text-sm font-medium
  shadow-sm
  transition-colors duration-150
  focus-visible:outline-none focus-visible:ring-2
  focus-visible:ring-error-600 focus-visible:ring-offset-2
  disabled:opacity-50 disabled:pointer-events-none
">
  <lucide-icon name="trash-2" [size]="16" aria-hidden="true" />
  Excluir Paciente
</button>

Tamanhos

TamanhoPaddingFont sizeÍcone
smpx-3 py-1.5text-xs14 px
md (padrão)px-4 py-2text-sm16 px
lgpx-5 py-2.5text-base18 px

Inputs e Formulários

Campo de Texto

html
<div class="flex flex-col gap-1.5">
  <label for="nome" class="text-sm font-medium text-neutral-700">
    Nome completo
    <span class="text-error-600 ml-0.5" aria-hidden="true">*</span>
  </label>

  <input
    id="nome"
    type="text"
    placeholder="Ex.: Ana Luíza Ferreira"
    class="
      w-full px-3 py-2 rounded-lg
      bg-white border border-neutral-200
      text-neutral-900 text-sm placeholder:text-neutral-400
      outline-none
      hover:border-neutral-300
      focus:border-brand-600 focus:ring-2 focus:ring-brand-100
      disabled:bg-neutral-50 disabled:text-neutral-400 disabled:cursor-not-allowed
      transition-colors duration-150
    "
  />

  <p class="text-xs text-neutral-500">
    Nome como aparece no cadastro oficial.
  </p>
</div>

Estado de Erro

html
<input class="
  /* ... classes base ... */
  border-error-500 ring-2 ring-error-100
  focus:border-error-500 focus:ring-error-100
" aria-describedby="nome-error" aria-invalid="true" />

<p id="nome-error" class="text-xs text-error-600 flex items-center gap-1">
  <lucide-icon name="alert-circle" [size]="12" aria-hidden="true" />
  Informe o nome completo do paciente.
</p>

Select

Mesmas dimensões e estados do input de texto. Use appearance-none + ícone chevron-down posicionado absolutamente.

Checkbox e Radio

html
<label class="flex items-center gap-2 cursor-pointer">
  <input
    type="checkbox"
    class="
      w-4 h-4 rounded
      border-2 border-neutral-300
      text-brand-600
      focus:ring-2 focus:ring-brand-100 focus:ring-offset-0
      cursor-pointer
    "
  />
  <span class="text-sm text-neutral-700">Notificar por e-mail</span>
</label>

Cards

Card Padrão

html
<div class="
  bg-white rounded-xl border border-neutral-200
  shadow-sm
  p-6
">
  <!-- conteúdo -->
</div>

Card de Stat (Dashboard)

html
<div class="
  bg-white rounded-xl border border-neutral-200
  shadow-sm p-6
  flex items-start justify-between
">
  <div>
    <p class="text-sm font-medium text-neutral-500">Agendamentos hoje</p>
    <p class="text-2xl font-semibold text-neutral-900 mt-1">24</p>
    <p class="text-xs text-success-600 flex items-center gap-1 mt-2">
      <lucide-icon name="trending-up" [size]="12" aria-hidden="true" />
      +12% vs. ontem
    </p>
  </div>
  <div class="p-2.5 bg-brand-50 rounded-lg">
    <lucide-icon name="calendar" [size]="20" class="text-brand-600" aria-hidden="true" />
  </div>
</div>

Card Interativo (hover)

Adicionar ao card padrão:

hover:border-neutral-300 hover:shadow-md
cursor-pointer transition-all duration-150

Badges / Tags

Badges indicam status. Seguem as paletas semânticas.

VarianteBackgroundTextoUso
defaultneutral-100neutral-700Estado neutro
brandbrand-50brand-700Destaque de produto
successsuccess-50success-700Concluído, ativo, pago
warningwarning-50warning-700Pendente, aguardando
errorerror-50error-700Cancelado, falha, vencido
infoinfo-50info-700Novo, informativo
html
<span class="
  inline-flex items-center gap-1
  px-2 py-0.5 rounded-full
  text-xs font-medium
  bg-success-50 text-success-700
">
  <span class="w-1.5 h-1.5 rounded-full bg-success-500" aria-hidden="true"></span>
  Concluído
</span>

Modais e Dialogs

Estrutura

html
<!-- Overlay -->
<div class="fixed inset-0 bg-neutral-950/60 z-50 flex items-center justify-center p-4">

  <!-- Container -->
  <div
    role="dialog"
    aria-modal="true"
    aria-labelledby="modal-title"
    class="
      bg-white rounded-2xl shadow-xl
      w-full max-w-md
      max-h-[90vh] overflow-y-auto
    "
  >
    <!-- Header -->
    <div class="flex items-start justify-between p-6 border-b border-neutral-100">
      <h2 id="modal-title" class="text-xl font-semibold text-neutral-900">
        Novo Agendamento
      </h2>
      <button aria-label="Fechar" class="text-neutral-400 hover:text-neutral-700">
        <lucide-icon name="x" [size]="20" aria-hidden="true" />
      </button>
    </div>

    <!-- Body -->
    <div class="p-6 space-y-4">
      <!-- conteúdo do formulário -->
    </div>

    <!-- Footer -->
    <div class="flex items-center justify-end gap-3 p-6 border-t border-neutral-100">
      <button class="/* ghost button */">Cancelar</button>
      <button class="/* primary button */">Salvar</button>
    </div>
  </div>
</div>

Tamanhos

Tamanhomax-wUso
smmax-w-sm (384 px)Confirmações destrutivas
mdmax-w-md (448 px)Formulários simples (padrão)
lgmax-w-lg (512 px)Formulários longos
xlmax-w-xl (576 px)Formulários complexos
2xlmax-w-2xl (672 px)Visualizadores de conteúdo

Tabelas

html
<div class="rounded-xl border border-neutral-200 overflow-hidden">
  <table class="w-full text-sm">
    <thead>
      <tr class="bg-neutral-50 border-b border-neutral-200">
        <th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wide">
          Paciente
        </th>
        <th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wide">
          Profissional
        </th>
        <th class="px-4 py-3 text-right text-xs font-medium text-neutral-500 uppercase tracking-wide">
          Ações
        </th>
      </tr>
    </thead>
    <tbody class="divide-y divide-neutral-100">
      <tr class="hover:bg-neutral-50 transition-colors duration-100">
        <td class="px-4 py-3 text-neutral-900 font-medium">Ana Luíza</td>
        <td class="px-4 py-3 text-neutral-600">Dr. Rafael Costa</td>
        <td class="px-4 py-3 text-right">
          <!-- ações inline -->
        </td>
      </tr>
    </tbody>
  </table>
</div>

Alerts e Toasts

Alert inline (dentro de formulário)

html
<div role="alert" class="
  flex items-start gap-3 p-4 rounded-lg
  bg-error-50 border border-error-100
  text-error-800 text-sm
">
  <lucide-icon name="alert-circle" [size]="16" class="mt-0.5 flex-shrink-0 text-error-500" aria-hidden="true" />
  <p>Não foi possível salvar. Verifique os campos marcados e tente novamente.</p>
</div>

Toast (notificação flutuante)

Posição: fixed bottom-4 right-4 z-50. Largura: max-w-sm. Auto-dismiss: 5 s.

html
<div role="status" aria-live="polite" class="
  flex items-start gap-3 p-4 rounded-xl
  bg-white border border-neutral-200 shadow-lg
  w-full max-w-sm
">
  <lucide-icon name="check-circle-2" [size]="18" class="mt-0.5 flex-shrink-0 text-success-500" aria-hidden="true" />
  <div class="flex-1">
    <p class="text-sm font-medium text-neutral-900">Agendamento criado</p>
    <p class="text-xs text-neutral-500">Quinta-feira, 12h30 — Ana Luíza</p>
  </div>
  <button aria-label="Fechar notificação" class="text-neutral-400 hover:text-neutral-700">
    <lucide-icon name="x" [size]="14" aria-hidden="true" />
  </button>
</div>

Empty States

html
<div class="flex flex-col items-center justify-center py-16 text-center">
  <div class="p-4 bg-neutral-100 rounded-2xl mb-4">
    <lucide-icon name="calendar" [size]="48" class="text-neutral-400" aria-hidden="true" />
  </div>
  <h3 class="text-base font-medium text-neutral-700 mb-1">
    Nenhum agendamento para hoje
  </h3>
  <p class="text-sm text-neutral-400 max-w-xs mb-6">
    A agenda está livre. Clique no botão abaixo para criar o primeiro agendamento do dia.
  </p>
  <button class="/* primary button */">
    <lucide-icon name="plus" [size]="16" aria-hidden="true" />
    Novo Agendamento
  </button>
</div>

Espaçamento

O sistema usa a escala de 4 px do Tailwind (spacing-1 = 4 px).

ContextoValorTailwind
Gap entre ícone e rótulo8 pxgap-2
Padding interno de botão8×16 pxpy-2 px-4
Padding interno de card24 pxp-6
Gap entre campos de formulário16 pxspace-y-4
Gap entre seções de formulário24 pxspace-y-6
Margem entre cards de dashboard16 pxgap-4
Padding de página24 pxp-6

Border Radius

ElementoValorTailwind
Botão8 pxrounded-lg
Input8 pxrounded-lg
Badge999 px (pílula)rounded-full
Card12 pxrounded-xl
Modal16 pxrounded-2xl
Ícone container (stat card)8 pxrounded-lg
Avatar50% (círculo)rounded-full

Sombras

ContextoClasseUso
shadow-smbox-shadow: 0 1px 2px rgba(0,0,0,0.05)Cards padrão, botões
shadow-mdbox-shadow: 0 4px 6px rgba(0,0,0,0.07)Cards hover, dropdowns
shadow-lgbox-shadow: 0 10px 15px rgba(0,0,0,0.10)Modais, toasts
shadow-xlbox-shadow: 0 20px 25px rgba(0,0,0,0.10)Overlays de alta prioridade

Não use sombras em botões de estado disabled. Não use shadow-xl em cards de interface operacional.

Desenvolvido com ❤️ pela equipe FastGivr.