SaaS Starter

Tokens

Variáveis OKLCH, sua classe Tailwind, e o sistema de tema duplo usado pelo dashboard.

Todos os tokens vivem em apps/client/app/globals.css. O sistema suporta dois temas via classe em <html>: .light e .dark. Cada nome de token resolve para um valor diferente por tema; a mesma utility do Tailwind (bg-card, text-foreground, border-border, etc.) faz a coisa certa nos dois contextos.

A escala base vem de uma engenharia reversa do Geist design system do vercel.com (research em docs/design-research/vercel/STYLE-GUIDE.md). O body é 15px (definido em html); as primitivas shadcn, text-sm (~13px), e text-base (15px) derivam daí.

Sempre referencie a classe semântica. Nunca hardcode o literal OKLCH em um componente.

Superfícies (elevadas sobre o bg da página)

TokenClasseLightDark
--backgroundbg-backgroundoklch(0.985 0 0) (~#FAFAFA)oklch(0 0 0) (#000)
--foregroundtext-foregroundoklch(0.21 0 0) (~#171717)oklch(0.94 0 0) (~#EDEDED)
--cardbg-cardoklch(1 0 0) (#FFF)oklch(0.087 0 0) (~#0A0A0A)
--popoverbg-popoveroklch(1 0 0)oklch(0.087 0 0)
--mutedbg-mutedoklch(0.95 0 0)oklch(0.21 0 0)
--muted-foregroundtext-muted-foregroundoklch(0.45 0 0)oklch(0.62 0 0)

O bg da página (--background) é a cor mais plana; cards, popovers e outras superfícies elevadas usam --card, que fica um passo de luminosidade na direção do inverso. Isso evita problemas de stacking de alpha quando um card está sobre outro.

CTA — paper-white sobre preto, preto sobre branco

TokenClasseLightDark
--primarybg-primaryoklch(0.21 0 0) (quase-preto)oklch(0.94 0 0) (paper-white)
--primary-foregroundtext-primary-foregroundoklch(0.98 0 0)oklch(0 0 0)
--accentbg-accentoklch(0 0 0 / 0.06)oklch(1 0 0 / 0.06)
--accent-foregroundtext-accent-foregroundoklch(0.21 0 0)oklch(0.94 0 0)

--primary é o fill inverso ao bg — a CTA canônica. --accent não é uma cor de marca; é o overlay de hover (transparente) que as primitivas shadcn usam para indicar estado ativo/hover. Para cor de marca use --brand.

Marca & info — Vercel blue

TokenClasseAmbos os temas
--brandbg-brand, text-brandoklch(0.591 0.227 256) (~hsla(212, 100%, 48%, 1))
--brand-foregroundtext-brand-foregroundoklch(0.98 0 0)
--infobg-info, text-infoalias de --brand
--ringring-ring--brand (focus rings usam a cor de marca)

Use --brand para links, badges informativos, highlights inline, e focus rings. Não use como bg de botão — os botões ficam paper-white/black via --primary. Isso reflete a separação do Vercel: o azul é "acento informativo", o contraste é "ação primária".

Cores de status

TokenClasseLightDark
--successbg-success, text-successoklch(0.55 0.13 152)oklch(0.62 0.135 152)
--warningbg-warning, text-warningoklch(0.74 0.16 70) (âmbar)igual
--destructivebg-destructive, text-destructiveoklch(0.59 0.21 22) (vermelho)igual

Use a primitiva <StatusBadge> (components/dashboard/status-badge.tsx) para renderizá-los consistentemente. Combine sempre cor + ícone/ponto + texto — nunca use só cor (regra de a11y do Vercel, ver STYLE-GUIDE §3.5).

Cores de stage (ambientes)

Reservadas para indicadores de ambiente quando expusermos stages de deploy. Nada usa ainda, mas documentadas para não as repintar inconsistentemente.

TokenClasseHexUso
--developtext-develop#0a72efAmbiente de desenvolvimento
--previewtext-preview#de1d8dDeployments de preview
--shiptext-ship#ff5b4fProdução

Bordas — box-shadow, não 1px solid

O padrão do Vercel: bordas de card são shadows, não border: 1px solid. Evita a matemática de box-sizing (a borda não consome espaço de layout) e compõe bem com drop shadows.

--border-hairline:        0 0 0 1px <alpha hairline>;
--border-hairline-inset:  inset 0 0 0 1px <alpha mais sutil>;
--border-hairline-large:  hairline + drop shadows para cards elevados;

Três utilities expostas em @layer utilities:

  • border-hairline — borda hairline outer, o default
  • border-hairline-inset — linha inner sutil (para headers de tabela, scroll containers)
  • border-hairline-lg — borda + drop shadow composto para cards flutuantes / popovers

Os componentes existentes que usam border-border (a versão 1px solid) continuam funcionando. Migre-os gradualmente quando forem reescritos — não reescreva primitivas shadcn intactas.

Charts

--chart-1 a --chart-5 são monocromos (foreground puro descendo até muted), com --chart-5 fazendo alias para --brand para ênfase quando uma série precisa se destacar.

Uma sub-escala pequena (--sidebar, --sidebar-foreground, --sidebar-border, --sidebar-accent, …) move alguns passos de luminosidade para que o rail de nav do dashboard se leia como uma superfície distinta sem quebrar o monocromático.

Utilities custom

Definidas em globals.css @layer utilities:

  • .label-mono — label mono pequeno em uppercase com letter-spacing 0.18em, foreground a 55% alpha. Envolva em <MonoLabel> (ver primitives).
  • .border-hairline, .border-hairline-inset, .border-hairline-lg — box-shadow-as-border (ver acima).
  • .shadow-soft / .shadow-soft-lg — drop shadows ajustadas para a base escura.
  • .card-premium / .card-lift / .card-glow — efeitos só de landing (UseDeploy hero cards). Não use no dashboard.

Escala de radius

--radius:      0.5rem (8px effective em base 15px)
--radius-sm:   calc(var(--radius) - 2px)
--radius-md:   var(--radius)
--radius-lg:   calc(var(--radius) + 2px)
--radius-xl:   calc(var(--radius) + 6px)

Use rounded-md para a maioria dos cards, rounded-sm para chips, rounded-lg para overlays flutuantes.

Tipografia

Geist Sans + Geist Mono são carregadas em app/layout.tsx. Use font-sans (default) ou font-mono. O body tem font-feature-settings: "cv11", "ss01" ativado para os stylistic alternates do Geist — não override globalmente.

O font-size do body é 15px em <html> — entre os 14 do Vercel (mais denso) e os 16 default (mais acessível). Todas as utilities text-* derivam dessa base.

Nesta página