Atomic UI Kit v2.2
atom · nível 1 de 3

Atoms

Elementos indivisíveis da interface. Cada átomo é a menor unidade funcional — não se decompõe sem perder significado. Copie qualquer bloco e use direto na sua stack.

HTML semântico Tailwind CSS Zero JS WCAG 2.1 AA
A01

Typography

Escala tipográfica. Hierarquia via tamanho, peso e espaçamento.

Display — 36px bold

text-4xl font-bold tracking-tight

Heading 1 — 24px bold

text-2xl font-bold

Heading 2 — 18px semibold

text-lg font-semibold

Body Medium — 16px medium

text-base font-medium

Body — 14px regular. Texto corrido com line-height relaxed para leitura confortável em parágrafos longos.

text-sm text-zinc-700 leading-relaxed

Caption — 12px · Metadados, hints, labels secundários

text-xs text-zinc-500

Mono — 12px · Código, IDs, valores técnicos

font-mono text-xs (JetBrains Mono)

SECTION LABEL — overline

text-xs font-semibold uppercase tracking-widest text-zinc-400

<!-- Display -->
<h1 class="text-4xl font-bold tracking-tight">
  Título principal
</h1>

<!-- Headings -->
<h2 class="text-2xl font-bold">Seção</h2>
<h3 class="text-lg font-semibold">Subseção</h3>

<!-- Body -->
<p class="text-sm text-zinc-700 leading-relaxed">
  Texto corrido.
</p>

<!-- Caption -->
<p class="text-xs text-zinc-500">Metadado</p>

<!-- Section label -->
<p class="text-xs font-semibold uppercase
     tracking-widest text-zinc-400">
  LABEL
</p>

<!-- Mono -->
<code class="font-mono text-xs text-zinc-600">
  valor-tecnico
</code>
A02

Color Tokens

Paleta semântica. Use o token de papel, não a cor direta.

Neutros

Foreground

zinc-900

Muted fg

zinc-600

Placeholder

zinc-400

Border

zinc-200

Surface alt

zinc-100

Surface

white

Status

Success

green-600

Success bg

green-50

Warning

yellow-500

Warning bg

yellow-50

Danger

red-600

Info

blue-600

Espaçamento

4px

p-1 / gap-1

8px

p-2 / gap-2

12px

p-3 / gap-3

16px

p-4 / gap-4

24px

p-6 / gap-6

32px

p-8 / gap-8

Border Radius

None

rounded-none

2px

rounded-sm

4px

rounded

6px

rounded-md

A03

Button

Sempre declare type e use texto descritivo. Nunca apenas "Clique aqui".

Variantes

Tamanhos

Estados

<!-- Primary -->
<button type="button"
  class="inline-flex items-center gap-2
         px-4 py-2 bg-zinc-900 text-white
         text-sm font-medium
         hover:bg-zinc-700
         focus:outline-none focus:ring-2
         focus:ring-zinc-900 focus:ring-offset-2
         transition-colors">
  Ação primária
</button>

<!-- Secondary -->
<button type="button"
  class="... bg-white text-zinc-900
         border border-zinc-300
         hover:border-zinc-500 hover:bg-zinc-50">
  Secundário
</button>

<!-- Ghost -->
<button type="button"
  class="... text-zinc-700
         hover:bg-zinc-100 hover:text-zinc-900">
  Ghost
</button>

<!-- Disabled -->
<button type="button" disabled
  class="... opacity-40 cursor-not-allowed">
  Desabilitado
</button>

<!-- Loading -->
<button type="button"
  class="... opacity-70 cursor-wait"
  aria-busy="true">
  <svg class="w-3.5 h-3.5 animate-spin"
       ...spinner path...></svg>
  Loading...
</button>
A04

Input

Sempre vincule <label for="id">. Nunca use placeholder como substituto de label.

<!-- Padrão -->
<div class="flex flex-col gap-1">
  <label for="campo"
         class="text-sm font-medium text-zinc-700">
    Rótulo
  </label>
  <input id="campo" type="text"
         class="w-full px-3 py-2 text-sm
                text-zinc-900 bg-white
                border border-zinc-300
                placeholder:text-zinc-400
                hover:border-zinc-400
                focus:outline-none focus:ring-2
                focus:ring-zinc-900
                focus:border-transparent
                transition-colors" />
</div>

<!-- Erro -->
<input aria-invalid="true"
       aria-describedby="msg"
       class="... border-red-500
              focus:ring-red-500" />
<p id="msg" role="alert"
   class="text-xs text-red-600
          flex items-center gap-1">
  Mensagem.
</p>

<!-- Disabled -->
<input disabled
       class="... bg-zinc-100 text-zinc-400
              border-zinc-200 cursor-not-allowed" />

<!-- Com ícone prefixo -->
<div class="relative">
  <!-- ícone absoluto pl-3 -->
  <input class="... pl-9" />
</div>
A05

Textarea

Use resize-none para consistência visual. Combine com contador de caracteres quando houver maxlength.

Aparece no seu perfil.

0 / 160

<!-- Padrão -->
<div class="flex flex-col gap-1">
  <label for="ta"
         class="text-sm font-medium text-zinc-700">
    Rótulo
  </label>
  <textarea id="ta" rows="3"
            class="w-full px-3 py-2 text-sm
                   text-zinc-900 bg-white
                   border border-zinc-300
                   placeholder:text-zinc-400
                   hover:border-zinc-400
                   focus:outline-none focus:ring-2
                   focus:ring-zinc-900
                   focus:border-transparent
                   transition-colors resize-none">
  </textarea>
</div>

<!-- Com contador de caracteres -->
<textarea maxlength="160" ...></textarea>
<div class="flex justify-between">
  <p class="text-xs text-zinc-500">Hint.</p>
  <p class="mono text-xs text-zinc-400">
    0 / 160
  </p>
</div>

<!-- Erro -->
<textarea aria-invalid="true"
          aria-describedby="msg"
          class="... border-red-500
                 focus:ring-red-500"></textarea>
<p id="msg" role="alert"
   class="text-xs text-red-600">
  Mensagem.
</p>
A06

Select

Use appearance-none + wrapper relativo para controlar o chevron decorativo.

<div class="flex flex-col gap-1">
  <label for="sel"
         class="text-sm font-medium text-zinc-700">
    Opção
  </label>
  <div class="relative">
    <select id="sel"
            class="w-full appearance-none
                   px-3 py-2 pr-9 text-sm
                   text-zinc-900 bg-white
                   border border-zinc-300
                   hover:border-zinc-400
                   focus:outline-none focus:ring-2
                   focus:ring-zinc-900
                   focus:border-transparent
                   transition-colors cursor-pointer">
      <option value="" disabled selected>
        Selecione
      </option>
      <option value="a">Opção A</option>
    </select>
    <!-- Chevron decorativo -->
    <div class="pointer-events-none absolute
                inset-y-0 right-0
                flex items-center px-3
                text-zinc-500">
      <!-- SVG chevron-down -->
    </div>
  </div>
</div>
A07

Checkbox

Técnica sr-only + peer para estilização total sem JS. Acessível nativamente.

<label class="flex items-center gap-3
              cursor-pointer group w-fit">

  <span class="relative flex items-center
               justify-center flex-shrink-0">
    <!-- Input real, invisível -->
    <input type="checkbox"
           class="peer sr-only" />

    <!-- Caixa visual via peer-* -->
    <span class="w-[18px] h-[18px]
                flex items-center justify-center
                border border-zinc-300 bg-white
                transition-all
                group-hover:border-zinc-500
                peer-focus-visible:ring-2
                peer-focus-visible:ring-zinc-900
                peer-focus-visible:ring-offset-2
                peer-checked:bg-zinc-900
                peer-checked:border-zinc-900">
      <svg class="w-2.5 h-2.5 text-white
                  opacity-0
                  peer-checked:opacity-100
                  transition-opacity"
           viewBox="0 0 10 8" fill="none">
        <path d="M1 4L3.5 6.5L9 1"
              stroke="currentColor"
              stroke-width="1.8"
              stroke-linecap="round"
              stroke-linejoin="round"/>
      </svg>
    </span>
  </span>

  <span class="text-sm text-zinc-700 select-none
               group-hover:text-zinc-900
               transition-colors">
    Rótulo
  </span>
</label>
A08

Radio

Seleção exclusiva. Agrupe em <fieldset> com <legend> para acessibilidade.

Plano de cobrança
<fieldset>
  <legend class="text-sm font-medium text-zinc-700">
    Grupo
  </legend>
  <label class="flex items-center gap-3
                cursor-pointer group w-fit">
    <span class="relative flex items-center
                 justify-center flex-shrink-0">
      <input type="radio"
             name="grupo" value="a"
             class="peer sr-only" />
      <!-- Anel externo -->
      <span class="w-[18px] h-[18px] rounded-full
                  border border-zinc-300 bg-white
                  group-hover:border-zinc-500
                  peer-checked:border-zinc-900
                  transition-all"></span>
      <!-- Ponto interno -->
      <span class="absolute w-2 h-2 rounded-full
                  bg-zinc-900 opacity-0
                  peer-checked:opacity-100
                  transition-opacity"></span>
    </span>
    Opção A
  </label>
</fieldset>
A09

Toggle / Switch

Use para ativar/desativar preferências com efeito imediato, sem botão Submit. Adicione role="switch" no input para acessibilidade.

<label class="flex items-center gap-3
              cursor-pointer group w-fit">
  <span class="relative flex-shrink-0">
    <input type="checkbox"
           role="switch"
           class="peer sr-only" />

    <!-- Trilha -->
    <span class="block w-9 h-5
                bg-zinc-200 border border-zinc-300
                transition-all
                peer-checked:bg-zinc-900
                peer-checked:border-zinc-900
                peer-focus-visible:ring-2
                peer-focus-visible:ring-zinc-900
                peer-focus-visible:ring-offset-2"></span>

    <!-- Bolinha deslizante -->
    <span class="absolute top-[3px] left-[3px]
                w-3.5 h-3.5
                bg-white border border-zinc-300
                transition-all
                peer-checked:translate-x-4
                peer-checked:border-zinc-100"></span>
  </span>
  Notificações
</label>

<!-- CORREÇÃO: role="switch" deve estar
  no <input>, não no <span> da trilha -->
A10

Badge

Rótulo de status ou categoria. Adicione role="status" + aria-live quando atualizar dinamicamente.

Sólido

Default Ativo Pendente Erro Info Inativo

Tonal (suave)

Default Ativo Pendente Erro Info
<!-- Sólido -->
<span class="inline-flex items-center
             px-2 py-0.5 text-xs font-medium
             bg-zinc-900 text-white">
  Label
</span>

<!-- Tonal com dot de status -->
<span class="inline-flex items-center gap-1.5
             px-2 py-0.5 text-xs font-medium
             bg-green-50 text-green-700
             border border-green-200">
  <span class="w-1.5 h-1.5 rounded-full
               bg-green-500"aria-hidden="true"></span>
  Ativo
</span>

<!-- Dinâmico (live region) -->
<span role="status"
      aria-live="polite"
      class="...">
  3 novos
</span>
A11

Tag / Chip

Rótulo interativo ou removível. Difere do Badge por ter ação de remoção ou seleção. Use aria-label descritivo no botão de fechar.

Estática

Design Frontend Aprovado

Removível

React Tailwind

Selecionável (toggle)

<!-- Estática -->
<span class="inline-flex items-center
             px-2.5 py-1 text-xs font-medium
             bg-zinc-100 text-zinc-700
             border border-zinc-200">
  Design
</span>

<!-- Removível -->
<span class="inline-flex items-center gap-1
             px-2.5 py-1 text-xs font-medium
             bg-zinc-100 text-zinc-700
             border border-zinc-200">
  React
  <button type="button"
          aria-label="Remover React"
          class="ml-0.5 text-zinc-400
                 hover:text-zinc-900
                 focus:outline-none focus:ring-1
                 focus:ring-zinc-900
                 transition-colors">
    <!-- SVG X 12×12 -->
  </button>
</span>

<!-- Selecionável (peer trick) -->
<label class="cursor-pointer">
  <input type="checkbox"
         class="peer sr-only" />
  <span class="inline-flex items-center
               px-2.5 py-1 text-xs font-medium
               border border-zinc-300 text-zinc-500
               bg-white hover:border-zinc-500
               peer-checked:bg-zinc-900
               peer-checked:text-white
               peer-checked:border-zinc-900
               transition-colors select-none">
    UX
  </span>
</label>
A12

Avatar

Iniciais como fallback universal. Sempre inclua aria-label no container para leitores de tela.

Tamanhos

6

8

10

12

16

Cores por usuário

AB
CD
EF
GH
IJ
KL

Com status indicator

AB
CD
EF
<!-- Iniciais (fallback) -->
<div class="w-10 h-10 bg-zinc-800
           flex items-center justify-center
           flex-shrink-0"
     aria-label="Avatar de Ana Barros">
  <span aria-hidden="true"
         class="text-sm font-semibold text-white">
    AB
  </span>
</div>

<!-- Com imagem -->
<div class="w-10 h-10 overflow-hidden
           flex-shrink-0">
  <img src="foto.jpg" alt="Ana Barros"
       class="w-full h-full object-cover" />
</div>

<!-- Com status indicator -->
<div class="relative w-fit">
  <!-- ...avatar acima... -->
  <span class="absolute -bottom-0.5 -right-0.5
               w-3 h-3 bg-green-500
               border-2 border-white"
         aria-label="Online"></span>
</div>
A13

Divider

Use <hr> para separação semântica. div para decorativo.

Padrão


Forte


Pontilhado


Com label


ou

Vertical

Item A Item B Item C
<!-- Padrão -->
<hr class="border-0 border-t border-zinc-200" />

<!-- Forte -->
<hr class="border-0 border-t-2 border-zinc-900" />

<!-- Pontilhado -->
<hr class="border-0 border-t border-dashed
          border-zinc-300" />

<!-- Com label central -->
<div class="flex items-center gap-3">
  <hr class="flex-1 border-0 border-t
            border-zinc-200" />
  <span class="text-xs text-zinc-400
               font-medium uppercase
               tracking-widest">ou</span>
  <hr class="flex-1 border-0 border-t
            border-zinc-200" />
</div>

<!-- Vertical (decorativo) -->
<div class="w-px h-full bg-zinc-200"
     role="separator"
     aria-orientation="vertical"></div>
A14

Skeleton

Placeholder de carregamento. Substitui o conteúdo enquanto os dados chegam. Animação animate-pulse do Tailwind.

Texto

Card com avatar

Linha de tabela

<!-- Wrapper com animate-pulse -->
<div class="animate-pulse"
     aria-busy="true"
     aria-label="Carregando...">

  <!-- Linha de texto -->
  <div class="h-4 bg-zinc-200 w-3/4"></div>
  <div class="h-4 bg-zinc-200 w-full mt-2"></div>

</div>

<!-- Card: avatar + linhas -->
<div class="flex items-start gap-3 animate-pulse">
  <div class="w-10 h-10 bg-zinc-200 flex-shrink-0"></div>
  <div class="flex-1 space-y-2 pt-1">
    <div class="h-3.5 bg-zinc-200 w-1/3"></div>
    <div class="h-3 bg-zinc-200 w-full"></div>
    <div class="h-3 bg-zinc-200 w-4/5"></div>
  </div>
</div>

<!-- Dica: use space-y-* dentro do wrapper
  em vez de mt-* em cada elemento -->
<div class="space-y-2 animate-pulse">
  <div class="h-4 bg-zinc-200 w-full"></div>
  <div class="h-4 bg-zinc-200 w-3/4"></div>
</div>
A15

Progress Bar

Use o elemento nativo <progress> para acessibilidade ou o padrão role="progressbar" com aria-valuenow.

Padrão

Upload 72%
Armazenamento 90%
Conclusão 38%

Espessuras

<!-- Com label + valor -->
<div>
  <div class="flex justify-between mb-1.5">
    <span class="text-xs font-medium text-zinc-700">
      Upload
    </span>
    <span class="mono text-xs text-zinc-500">
      72%
    </span>
  </div>

  <!-- Trilha -->
  <div class="w-full h-1.5 bg-zinc-200"
       role="progressbar"
       aria-valuenow="72"
       aria-valuemin="0"
       aria-valuemax="100"
       aria-label="Upload: 72%">
    <!-- Preenchimento -->
    <div class="h-full bg-zinc-900 transition-all"
         style="width: 72%"></div>
  </div>
</div>

<!-- Cores de estado:
  Padrão  → bg-zinc-900
  Perigo  → bg-red-500 (uso alto)
  Info    → bg-blue-500
  Sucesso → bg-green-500
-->

<!-- Espessuras: h-1 h-1.5 h-2.5 h-4 -->
A16

Tooltip

CSS puro via group + group-hover. Adicione role="tooltip" e aria-describedby no trigger.

Posições (passe o mouse)

<!-- Wrapper: relative + group -->
<div class="relative group">

  <!-- Trigger -->
  <button type="button"
          aria-describedby="tt-id"
          class="...">
    Hover aqui
  </button>

  <!-- Tooltip: topo -->
  <div id="tt-id"
       role="tooltip"
       class="pointer-events-none absolute
              bottom-full left-1/2
              -translate-x-1/2 mb-2
              px-2.5 py-1.5
              bg-zinc-900 text-white
              text-xs font-medium whitespace-nowrap
              opacity-0
              group-hover:opacity-100
              group-focus-within:opacity-100
              transition-opacity z-50">
    Texto da dica
    <span class="absolute top-full left-1/2
                -translate-x-1/2
                border-4 border-transparent
                border-t-zinc-900"></span>
  </div>
</div>

<!-- Posições — troque as classes:
  Topo:     bottom-full mb-2
  Base:     top-full mt-2
  Esq.:     right-full mr-2
  Dir.:     left-full ml-2
-->
REF

Quick Reference

Classes recorrentes em todos os átomos. Memorize uma vez, aplique em tudo.

Token Classes Tailwind Onde usar
focus-ringfocus:outline-none focus:ring-2 focus:ring-zinc-900 focus:ring-offset-2Todos os interativos
focus-ring-errorfocus:ring-red-500Substitui em estado de erro
input-baseborder border-zinc-300 bg-white text-zinc-900Input, Textarea, Select
input-hoverhover:border-zinc-400Feedback sutil no hover
input-errorborder-red-500Estado inválido
input-disabledbg-zinc-100 text-zinc-400 cursor-not-allowedCampo desabilitado
field-wrapperflex flex-col gap-1Label + Input + Hint/Error
btn-primarybg-zinc-900 text-white hover:bg-zinc-700Ação principal
peer-trickpeer sr-only + peer-checked:* / peer-focus-visible:*Checkbox, Radio, Toggle, Tag
error-msgtext-xs text-red-600 + role="alert"Mensagem de erro acessível
switch-rolerole="switch" no <input>Toggle/Switch
progress-barrole="progressbar" aria-valuenow aria-valuemin aria-valuemaxProgress Bar
A17

Spinner

Indicador de carregamento standalone. Use aria-label e role="status". Animação via Tailwind animate-spin.

Tamanhos

Variantes de cor

Com texto inline

Carregando dados...
<!-- Standalone -->
<span role="status"
      aria-label="Carregando"
      class="inline-block w-6 h-6
             border-2 border-zinc-200
             border-t-zinc-900
             rounded-full animate-spin">
</span>

<!-- Tamanhos:
  sm → w-4 h-4 border-2
  md → w-6 h-6 border-2
  lg → w-8 h-8 border-[3px]
  xl → w-10 h-10 border-4
-->

<!-- Cor no escuro (fundo dark) -->
<span class="... border-white/30
             border-t-white"></span>

<!-- Dentro de botão loading -->
<button disabled aria-busy="true"
        class="inline-flex items-center gap-2
               ... opacity-70 cursor-wait">
  <span role="status" aria-label="Enviando"
        class="inline-block w-3.5 h-3.5
               border-2 border-white/30
               border-t-white
               rounded-full animate-spin"></span>
  Enviando...
</button>
A19

Range / Slider

Entrada numérica contínua. Combina com <label> e valor exibido. Estilize via accent-color nativo.

72%
0%100%
R$ 2.500
<div class="flex flex-col gap-3">
  <div class="flex items-center justify-between">
    <label for="range"
           class="text-sm font-medium text-zinc-700">
      Label
    </label>
    <span class="mono text-xs text-zinc-500">72%</span>
  </div>
  <input id="range" type="range"
         min="0" max="100" value="72"
         class="w-full h-1.5 bg-zinc-200
                appearance-none cursor-pointer
                accent-zinc-900
                [&::-webkit-slider-thumb]:w-4
                [&::-webkit-slider-thumb]:h-4
                [&::-webkit-slider-thumb]:appearance-none
                [&::-webkit-slider-thumb]:bg-zinc-900
                [&::-webkit-slider-thumb]:border-2
                [&::-webkit-slider-thumb]:border-white
                [&::-webkit-slider-thumb]:rounded-full" />
  <div class="flex justify-between
              mono text-[10px] text-zinc-400">
    <span>0%</span><span>100%</span>
  </div>
</div>
A20

Code Block

Bloco de código com header de linguagem e botão copiar. Use <pre><code> para semântica correta.

JavaScript
async function fetchUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error('Not found');
  return res.json();
}
Bash
$ npm install && npm run dev
const x = 42;
<!-- Com header -->
<div class="border border-zinc-200 overflow-hidden">
  <div class="flex items-center justify-between
              px-4 py-2 bg-zinc-100
              border-b border-zinc-200">
    <span class="mono text-[10px] font-semibold
               text-zinc-500 uppercase tracking-widest">
      JavaScript
    </span>
    <button type="button"
            class="mono text-[10px] text-zinc-400
                   hover:text-zinc-700 transition-colors">
      copiar
    </button>
  </div>
  <pre class="bg-zinc-950 p-4 overflow-x-auto m-0">
    <code class="mono text-xs
                 text-zinc-300 leading-relaxed">
      ...
    </code>
  </pre>
</div>

<!-- Sem header (inline simples) -->
<pre class="bg-zinc-950 p-4
          overflow-x-auto border border-zinc-800">
  <code class="mono text-xs text-zinc-300">
    código aqui
  </code>
</pre>
A21

Stepped Progress

Barra de progresso por etapas discretas. Variante do A15 para fluxos multi-step.

Segmentos

Etapa 2 de 4

Com ícones e labels

Conta

2

Plano

3

Pagamento

4

Confirmar

<!-- Segmentos simples -->
<div class="flex gap-1"
     role="progressbar"
     aria-valuenow="2" aria-valuemin="0"
     aria-valuemax="4"
     aria-label="Etapa 2 de 4">
  <!-- Concluído → bg-zinc-900 -->
  <div class="flex-1 h-1.5 bg-zinc-900"></div>
  <div class="flex-1 h-1.5 bg-zinc-900"></div>
  <!-- Pendente → bg-zinc-200 -->
  <div class="flex-1 h-1.5 bg-zinc-200"></div>
  <div class="flex-1 h-1.5 bg-zinc-200"></div>
</div>

<!-- Com ícones:
  Concluído: bg-zinc-900 + SVG check
  Atual:     bg-zinc-900 + ring-2 ring-offset-2
  Pendente:  border-2 border-zinc-200 + número
  Conector concluído: bg-zinc-900 h-px mt-3.5
  Conector pendente:  bg-zinc-200 h-px mt-3.5
-->