SaaS Starter

Tokens

Variables OKLCH, su clase de Tailwind, y el sistema dual-tema usado por el dashboard.

Todos los tokens viven en apps/client/app/globals.css. El sistema soporta dos temas vía clase en <html>: .light y .dark. Cada nombre de token resuelve a un valor distinto por tema; la misma utility de Tailwind (bg-card, text-foreground, border-border, etc.) hace lo correcto en ambos contextos.

La escala base viene de un reverse-engineering del Geist design system de vercel.com (research en docs/design-research/vercel/STYLE-GUIDE.md). El body es 15px (seteado en html); las primitivas shadcn, text-sm (~13px), y text-base (15px) derivan de ahí.

Siempre referenciá la clase semántica. Nunca hardcodees el literal OKLCH en un componente.

Superficies (elevadas sobre el bg de la página)

TokenClaseLightDark
--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)

El bg de página (--background) es el color más plano; cards, popovers y demás superficies elevadas usan --card, que está un paso de luminosidad hacia el inverso. Esto evita problemas de stacking de alpha cuando una card va sobre otra.

CTA — paper-white sobre negro, negro sobre blanco

TokenClaseLightDark
--primarybg-primaryoklch(0.21 0 0) (casi-negro)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 es el fill inverso al bg — la CTA canónica. --accent no es un color de marca; es el overlay de hover (transparente) que las primitivas shadcn usan para indicar estado activo/hover. Para color de marca usá --brand.

Marca e info — Vercel blue

TokenClaseAmbos 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 (los focus rings usan el color de marca)

Usá --brand para links, badges informativos, highlights inline, y focus rings. No lo uses como bg de botón — los botones se quedan paper-white/black vía --primary. Esto refleja la separación de Vercel: el azul es "acento informativo", el contraste es "acción primaria".

Colores de estado

TokenClaseLightDark
--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) (rojo)igual

Usá la primitiva <StatusBadge> (components/dashboard/status-badge.tsx) para renderizarlos consistentemente. Combiná siempre color + ícono/punto + texto — nunca uses sólo color (regla de a11y de Vercel, ver STYLE-GUIDE §3.5).

Colores de stage (entornos)

Reservados para indicadores de entorno cuando expongamos stages de deploy. No los usa nada todavía, pero documentados así no los repintamos inconsistentemente.

TokenClaseHexUso
--developtext-develop#0a72efEntorno de desarrollo
--previewtext-preview#de1d8dDeployments de preview
--shiptext-ship#ff5b4fProducción

Bordes — box-shadow, no 1px solid

El patrón de Vercel: los bordes de card son shadows, no border: 1px solid. Evita la matemática de box-sizing (el border no consume espacio de layout) y compone bien con drop shadows.

--border-hairline:        0 0 0 1px <alpha hairline>;
--border-hairline-inset:  inset 0 0 0 1px <alpha más sutil>;
--border-hairline-large:  hairline + drop shadows para cards levantadas;

Tres utilities expuestas en @layer utilities:

  • border-hairline — borde hairline outer, el default
  • border-hairline-inset — línea inner sutil (para headers de tabla, scroll containers)
  • border-hairline-lg — borde + drop shadow compuesto para cards flotantes / popovers

Los componentes existentes que usan border-border (la versión 1px solid) siguen andando. Migralos gradualmente cuando se reescriban — no reescribas primitivas shadcn intactas.

Charts

--chart-1 a --chart-5 son monocromos (foreground puro descendiendo a muted), con --chart-5 aliasing a --brand para énfasis cuando una serie tiene que destacar.

Una sub-escala chica (--sidebar, --sidebar-foreground, --sidebar-border, --sidebar-accent, …) corre un par de pasos de luminosidad para que el rail de nav del dashboard se lea como una superficie distinta sin romper el monocromo.

Utilities custom

Definidas en globals.css @layer utilities:

  • .label-mono — label mono pequeño en uppercase con letter-spacing 0.18em, foreground al 55% alpha. Wrappealo en <MonoLabel> (ver primitives).
  • .border-hairline, .border-hairline-inset, .border-hairline-lg — box-shadow-as-border (ver arriba).
  • .shadow-soft / .shadow-soft-lg — drop shadows tuneadas para la base oscura.
  • .card-premium / .card-lift / .card-glow — efectos sólo de landing (UseDeploy hero cards). No los uses en el dashboard.

Escala de radius

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

Usá rounded-md para la mayoría de cards, rounded-sm para chips, rounded-lg para overlays flotantes.

Tipografía

Geist Sans + Geist Mono se cargan en app/layout.tsx. Usá font-sans (default) o font-mono. El body tiene font-feature-settings: "cv11", "ss01" activado para los stylistic alternates de Geist — no lo overrideés globalmente.

El font-size del body es 15px en <html> — entre los 14 de Vercel (más denso) y los 16 default (más accesible). Todas las utilities text-* derivan de esa base.

En esta página