Appearance
Design System
Cards
IMPORTANTE: Cards não possuem bordas no sistema. Use apenas background e border-radius.
Errado:
tsx
<View className="bg-card rounded-lg border border-border p-4">Correto:
tsx
<View className="bg-card rounded-[6px] p-4">O componente components/ui/Card já segue isso (bg-card, rounded-[6px], shadow-sm).
Tokens semânticos (não usar cor crua)
Os componentes consomem classes semânticas mapeadas para CSS variables HSL (ver styling.md). Nunca hardcode hex/neutral-* para superfícies e texto — use o token, que já troca entre light/dark e segue o tema paper.
| Elemento | Classe | Light (paper) | Dark (preto neutro) |
|---|---|---|---|
| Fundo da tela | bg-background | #f5efde | #0A0A0A |
| Card / superfície | bg-card | #fefcf6 | #1A1A1A |
| Sheet / popover | bg-popover | #fefcf6 | #1F1F1F |
| Superfície sutil | bg-muted | #f0e8d6 | #242424 |
| Campo de formulário | bg-input | #ece5cf | #333333 |
| Texto primário | text-foreground | #342d23 | #F5F5F5 |
| Texto secundário | text-muted-foreground | #70634c | #A8A8A8 |
| Borda/divisor | border-border | #ece5cf | #3D3D3D |
| Ação primária | bg-primary | azul da marca | azul da marca |
| Destaque | bg-secondary | laranja | laranja |
No dark o tema é preto neutro (saturação 0). Como o card não tem borda, a separação em relação ao fundo vem só do degrau de lightness. Ver a tabela de elevação em styling.md.
O mesmo token resolve automaticamente para o valor light ou dark conforme a classe
.dark(alternada pelouseColorScheme/setColorScheme). Não escreva variantesdark:bg-...manuais para essas superfícies — o token já cuida disso.
Cor em prop (ícones)
Para cor passada como prop (ex.: ícones Lucide), use o objeto THEME:
tsx
const { colorScheme } = useColorScheme()
const theme = THEME[colorScheme ?? 'light']
<SlidersHorizontal size={18} color={theme.mutedForeground} />Campos de formulário
Campos que aparecem juntos numa mesma tela (input de texto, seletor de data, SelectField) devem compartilhar altura, raio, borda e superfície. Divergir em h-9 vs h-11 ou em bg-muted/20 vs bg-muted/30 deixa a tela desalinhada e, no dark, dissolve o campo no card.
O padrão é extraído numa constante do módulo e reaproveitado por todos os campos da tela, ex. modules/contracts/constants/contract-setup-field-layout.ts:
ts
export const CONTRACT_SETUP_FIELD_CLASS_NAME =
'h-11 flex-row items-center rounded-xl border border-border bg-muted/40 px-3 dark:bg-input'tsx
<Input className={cn(CONTRACT_SETUP_FIELD_CLASS_NAME, CONTRACT_SETUP_FIELD_TEXT_CLASS_NAME)} />
<Pressable className={cn(CONTRACT_SETUP_FIELD_CLASS_NAME, 'min-w-0 flex-1 gap-2')} />
<SelectField className={CONTRACT_SETUP_FIELD_CLASS_NAME} />No dark o preenchimento é sempre dark:bg-input sólido: opacidades (/20, /30) não geram contraste suficiente contra bg-card.
Checkbox de campo (ex.: vigência indeterminada) usa components/ui/Checkbox numa linha própria, nunca espremido ao lado dos campos.
Status (badges)
As cores de status (success, warning, destructive, info) são mantidas em ambos os temas. O components/ui/StatusBadge mapeia o tom para as classes correspondentes. Ex.: template publicado → success; rascunho → warning.
Tipografia
Fonte nativa do sistema (sem fonte customizada no mobile). Hierarquia via components/ui/Text com variantes (h1–h4, p, small, muted, ...) e classes Tailwind de tamanho/peso. Não usar Noto Serif/serif aqui — isso é exclusivo da web.
Componentes de UI
- Primitivos em
components/ui/(Button, Card, Text, StatusBadge, Toast, ...). - Variantes com
class-variance-authority(cva) e composição de classes comcn(). - Ícones:
lucide-react-native.
Ações em telas com SidebarLayout
Telas com abas inferiores (SidebarLayout) e mais de 2 ações usam ExpandableActionsFab (FAB flutuante expansível com pills e labels), posicionado acima da tab bar.
tsx
const fabBottomOffset = useSidebarEditorFabOffset(compact)
const fabActions = mapEditorHeaderActionsToFab(headerActions)
<ExpandableActionsFab actions={fabActions} bottomOffset={fabBottomOffset} disabled={isSaving} />Referência: Feature Map — FAB.
Listagens com uma ação primária (criar) usam BorrowerFab. Editores com ≤ 2 ações podem manter ícones no header via EditorHeaderActions.