v1 — Em desenvolvimento

Design System

A linguagem visual, biblioteca de componentes e tokens de design do UpForService. Construída para consistência, acessibilidade e escala.

Introdução

O Design System do UpForService é a fonte única de verdade para a linguagem visual do produto. Ele fornece a designers e engenheiros tokens, padrões e diretrizes compartilhados para construir interfaces consistentes e acessíveis em escala.

Tokens em primeiro lugar

Cada cor, tamanho e sombra é um token nomeado. Nenhum valor mágico nos componentes.

Composável

Primitivos compõem padrões. Padrões compõem páginas.

Acessível

Contraste WCAG AA, navegação por teclado e HTML semântico em todo lugar.

Princípios de Design

Quatro princípios guiam cada decisão no Design System do UpForService.

01

Confiança pela clareza

Transações financeiras e contratos de serviço exigem comunicação inequívoca. Cada elemento de interface deve ser claro sobre o que faz e o que acontecerá a seguir.

02

Simples na UI, rico no domínio

A UI expõe o caminho feliz. O modelo de domínio carrega versionamento, histórico de eventos e máquinas de estado desde o início. A complexidade vive na camada certa.

03

Acessível por padrão

Contraste, navegação por teclado, focus rings e HTML semântico não são recursos opcionais. São requisitos de base para cada componente.

04

Preparado para internacionalização

Cada string é uma chave de tradução. Layouts levam em conta a expansão do texto. Formatos de moeda e número seguem as convenções do locale.

Cores

Todas as cores são definidas como propriedades CSS customizadas em globals.css e mapeadas para o Tailwind via @theme inline. A paleta suporta temas claro e escuro.

Marca

Primary #0A6FFF: 3.0:1 on white (AA-Large) · use --primary-text for body text

Primária

--primary

#0A6FFF

Hover primária

--primary-hover

#0858D4

Texto primário

--primary-text

#0858D4

Terciária

--tertiary

#7C3AED

Fundo e Superfície

Fundo

--background

#FFFFFF

Azul suave

--surface-pale-blue

#F8FAFF

Azul tintado

--surface-tint-blue

#F0F5FF

Frio

--surface-cool

#F8FAFC

Navy

--surface-navy

#0A1628

Navy médio

--surface-navy-mid

#0D1F3C

Texto

All text tokens: 4.5:1+ AA on white background

Título

--heading

#111827

Primeiro plano

--foreground

#1F2937

Text secondary

--text-secondary

#4B5563

Silenciado

--muted-foreground

#6B7280

Quieto

--quiet

#9CA3AF

Chrome de UI

Card

--card

#FFFFFF

Surface secondary

--secondary

#F8FAFC

Destaque

--accent

#F0F5FF

Borda

--border

#E5E7EB

Input

--input

#E5E7EB

Superfície suave

--muted

#F8FAFF

Status

All status foreground tokens meet 4.5:1 AA on their respective bg tokens

Destrutivo

--destructive

#EF4444

Aviso

--warning

#D97706

Sucesso

--success

#059669

Informação

--info

#2563EB

Fundo aviso

--warning-bg

#FFFBEB

Fundo sucesso

--success-bg

#ECFDF5

Desabilitado

Desabilitado

--disabled

#E5E7EB

Texto desabilitado

--disabled-foreground

#9CA3AF

Modo escuro

Todos os tokens têm overrides para modo escuro na classe .dark em globals.css. O tema escuro usa a mesma paleta navy das seções de marketing, mantendo a consistência visual entre os modos.

Tipografia

Três famílias de fontes são carregadas via otimização de fontes do Next.js. Bricolage Grotesque para títulos de exibição, Inter para corpo e UI, e JetBrains Mono para código e valores cripto.

Famílias de fontes

Display

Aa Bb Cc

Bricolage Grotesque

--font-display

Títulos, marketing

Sans

Aa Bb Cc

Inter

--font-sans

Corpo, labels de UI

Mono

Aa Bb Cc

JetBrains Mono

--font-mono

Código, endereços cripto

Escala tipográfica

Display XL

48–72px · 800 · display

Marketplace

Display LG

36–48px · 800 · display

Encontre seu especialista

Display MD

30–36px · 700 · display

Protegido pela Solana

H1

36px · 700 · display

Título da página

H2

30px · 700 · display

Cabeçalho de seção

H3

24px · 600 · display

Sub-seção

H4

20px · 600 · sans

Cabeçalho de card

Body large

18px · 400 · sans

O marketplace para serviços profissionais estruturados.

Body

16px · 400 · sans

Escopo claro, pagamento protegido, reputação pública.

Body small

14px · 400 · sans

Seu pagamento fica em custódia até a conclusão do serviço.

Caption

12px · 400 · sans

Atualizado há 3 minutos · Ref #UFS-20240115

Label

14px · 500 · sans

Categoria de serviço

Mono

14px · 400 · mono

7eTp3xKmN9...8sQwZ · 0.42 SOL

Espaçamentos

Grid base de 4px. Todos os valores são múltiplos de 4px. Use a escala do Tailwind diretamente — p-4 = 16px, gap-6 = 24px.

TokenRemVisual
space-0
0rem
0px
space-1
0.25rem
4px
space-2
0.5rem
8px
space-3
0.75rem
12px
space-4
1rem
16px
space-5
1.25rem
20px
space-6
1.5rem
24px
space-8
2rem
32px
space-10
2.5rem
40px
space-12
3rem
48px
space-16
4rem
64px
space-20
5rem
80px
space-24
6rem
96px
space-32
8rem
128px
space-40
10rem
160px
space-48
12rem
192px
space-64
16rem
256px

Borda e Raio

Os tokens de raio são definidos como propriedades CSS customizadas e mapeados para o Tailwind via @theme inline. Use rounded-pill, rounded-card, rounded-md, rounded-sm, rounded-xs.

Nenhum

--radius-none

0px

xs

--radius-xs

6px

sm

--radius-sm

10px

md

--radius-md

16px

Card / lg

--radius-card

24px

Pílula / full

--radius-pill

9999px

Espessuras de borda

border

1px

border-2

2px

border-4

4px

Sombras

Sombras são nomeadas semanticamente. Use shadow-raised para elementos elevados, shadow-feature-panel para cards de marketing, e shadow-cta-glow para botões de ação primária.

raised

--shadow-raised

feature-panel

--shadow-feature-panel

marketing

--shadow-marketing

cta-glow

--shadow-cta-glow

Anel de foco

Todos os elementos interativos usam --ring para visibilidade de foco. Aplicado via focus-visible:ring-[3px] focus-visible:ring-ring/50.

Escala de Z-index

Definida no JSON de tokens. Use esses valores para manter ordem de empilhamento consistente em toda a aplicação.

CamadaValorUso
base0Fluxo padrão do documento
raised10Cards levemente elevados, cabeçalhos sticky dentro do fluxo
dropdown1000Menus, comboboxes, dropdowns de seleção
sticky1100Barra de navegação sticky, sidebar de filtros
fixed1200Banners fixos, barras de anúncio
modal1300Overlays de dialog
popover1400Popovers, date pickers, paleta de comandos
tooltip1500Tooltips
toast1600Notificações toast (Sonner)

Breakpoints & Grid

Design responsivo mobile-first. Escala de breakpoints padrão do Tailwind com container de conteúdo max-w-[1200px] e max-w-[1400px] para navegação em largura total.

PrefixoLargura mín.Contexto típico
0pxRetrato mobile (base, sem prefixo)
sm:640pxPaisagem mobile, tablets pequenos
md:768pxTablet retrato
lg:1024pxLaptop / desktop
xl:1280pxDesktop amplo
2xl:1536pxUltra-wide

Container & gutters

Largura máx. conteúdo

1200px

Páginas, seções

Largura máx. nav

1400px

Nav superior, rodapé

Gutter (mobile)

px-4 (16px)

Todos os viewports

Gutter (md+)

px-6 (24px)

Tablet para cima

Colunas do grid

12

Base CSS Grid

Componentes

Construído sobre primitivos shadcn/ui com Radix UI. Todos os componentes usam class-variance-authority para variantes e aceitam uma prop className para extensão.

Button

Elemento interativo primário. Usa rounded-pill por padrão. Suporta asChild para renderizar como link via Slot.

Variantes

Tamanhos

Estados

import { Button } from '@/components/ui/button'
import Link from 'next/link'

// Default
<Button>Get started</Button>

// Variant + size
<Button variant="outline" size="sm">Cancel</Button>
<Button variant="destructive">Delete offer</Button>

// As Next.js link (asChild)
<Button asChild>
  <Link href="/dashboard">Go to dashboard</Link>
</Button>

// Icon button – always include aria-label
<Button size="icon" aria-label="Share profile">
  <Share2 className="size-4" />
</Button>
Accessibility: Sempre use um aria-label descritivo em botões com apenas ícone. Botões desabilitados usam aria-disabled e pointer-events-none.

Badge

Label inline para status, categorias e metadados. Usa rounded-full e suporta asChild.

Variantes

DefaultSecondaryOutlineGhostDestructiveLinkEyebrow — category label

Exemplos de status

ConfirmadoPendenteFalhouEm andamento
import { Badge } from '@/components/ui/badge'

<Badge>Active</Badge>
<Badge variant="destructive">Failed</Badge>
<Badge variant="outline">Pending</Badge>
<Badge variant="secondary">Draft</Badge>

// Status with icon
<Badge className="bg-success text-success-foreground">
  <CheckCircle2 className="size-3" />
  Confirmed
</Badge>

// Eyebrow — purely typographic, no visual container
<Badge variant="eyebrow">Service category</Badge>

Card

Container para conteúdo agrupado. Usa rounded-card (24px). Sub-componentes: CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter.

Padrão

Oferta de serviço
Auditoria e otimização de performance React
Ativo

Revisão detalhada dos ciclos de renderização, tamanho do bundle e Core Web Vitals do seu app React.

Feature (elevado)

Pagamento protegido
O escrow Solana retém seus fundos até a entrega

Os fundos são liberados somente após você confirmar que o serviço foi entregue conforme acordado.

import { Card, CardHeader, CardTitle, CardDescription,
  CardAction, CardContent, CardFooter } from '@/components/ui/card'

// Default
<Card>
  <CardHeader>
    <CardTitle>Service offer</CardTitle>
    <CardDescription>React performance audit</CardDescription>
    <CardAction><Badge>Active</Badge></CardAction>
  </CardHeader>
  <CardContent>Description goes here.</CardContent>
  <CardFooter><Button>View details</Button></CardFooter>
</Card>

// Feature (elevated shadow)
<Card variant="feature">...</Card>

Avatar

Representação de usuário com imagem, fallback de iniciais e suporte a badge. Construído sobre o primitivo Avatar do Radix UI.

Tamanhos

SMsm
MDmd
LGlg
LBimage

Grupo de avatars

ABCDEF
+12

Cores de fundo determinísticas

ABCDEFGHIJKLMNOP

8 fundos em OKLCH L=0.45 C=0.10 — todos passam 4.5:1 WCAG AA contra iniciais brancas.

import { Avatar, AvatarImage, AvatarFallback,
  AvatarGroup, AvatarGroupCount } from '@/components/ui/avatar'

// With image + fallback
<Avatar size="lg">
  <AvatarImage src="/photo.jpg" alt="Lucy Brandão" />
  <AvatarFallback
    style={{ backgroundColor: 'var(--avatar-bg-3)', color: 'var(--avatar-fg)' }}>
    LB
  </AvatarFallback>
</Avatar>

// Group
<AvatarGroup>
  <Avatar><AvatarFallback>AB</AvatarFallback></Avatar>
  <Avatar><AvatarFallback>CD</AvatarFallback></Avatar>
  <AvatarGroupCount>+5</AvatarGroupCount>
</AvatarGroup>

Campo de texto

Input de texto de uma linha. Compartilha tokens de foco, erro e desabilitado de forma consistente entre todos os controles de formulário.

Padrão

Com texto auxiliar

Visível na sua página de perfil público.

Estado de erro

Desabilitado

Endereço bloqueado após verificação.

import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'

// Default
<div className="space-y-2">
  <Label htmlFor="title">Service title</Label>
  <Input id="title" placeholder="React performance audit" />
</div>

// Error state
<div className="space-y-2">
  <Label htmlFor="email" className="text-destructive">Email</Label>
  <Input id="email" aria-invalid="true"
    className="border-destructive focus-visible:ring-destructive/20" />
  <p className="text-xs text-destructive" role="alert">
    Enter a valid email address.
  </p>
</div>

// Disabled
<Input disabled value="locked@value.com" />
Accessibility: Todo input deve ter um Label associado (via htmlFor) ou um aria-label. Mensagens de erro usam aria-invalid="true".

Select

Seleção por dropdown construída sobre o Radix UI Select. Navegável por teclado, suporta grupos, labels e itens desabilitados.

Padrão

Com grupos

Estado de erro

Desabilitado

import { Select, SelectTrigger, SelectValue, SelectContent,
  SelectItem, SelectGroup, SelectLabel, SelectSeparator
} from '@/components/ui/select'

<Select>
  <SelectTrigger className="w-full">
    <SelectValue placeholder="Select category" />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Technology</SelectLabel>
      <SelectItem value="web">Web development</SelectItem>
      <SelectItem value="mobile">Mobile apps</SelectItem>
    </SelectGroup>
    <SelectSeparator />
    <SelectGroup>
      <SelectLabel>Creative</SelectLabel>
      <SelectItem value="design">Graphic design</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>
Accessibility: Teclas de seta navegam as opções, Enter/Espaço seleciona. Use SelectLabel e SelectSeparator para opções agrupadas. Itens desabilitados usam data-[disabled].

Textarea

Input de texto de múltiplas linhas. Usa field-sizing-content para crescimento automático de altura. Compartilha os mesmos tokens de foco, erro e desabilitado que o Campo de texto.

Padrão

Máximo de 2 000 caracteres.

Com texto auxiliar

Sua avaliação será pública no perfil do prestador.

Estado de erro

Desabilitado

Notas internas são visíveis apenas para você.

import { Textarea } from '@/components/ui/textarea'
import { Label } from '@/components/ui/label'

<div className="space-y-2">
  <Label htmlFor="desc">Service description</Label>
  <Textarea id="desc"
    placeholder="Describe what's included…"
  />
  <p className="text-xs text-muted-foreground">
    Maximum 2 000 characters.
  </p>
</div>
Accessibility: Sempre use com Label. Estado de erro usa aria-invalid="true". O auto-resize via field-sizing-content não precisa de JavaScript.

Separator

Divisor visual fino. Horizontal por padrão; use orientation="vertical" para contextos inline.

Horizontal

Section A

Section B

Section C

Vertical

Overview
Reviews
Questions
import { Separator } from '@/components/ui/separator'

// Horizontal (default) — between sections
<Separator />

// Vertical — between inline items
<div className="flex items-center gap-4 h-8">
  <span>Overview</span>
  <Separator orientation="vertical" />
  <span>Reviews</span>
</div>

// Semantic (not decorative)
<Separator decorative={false} aria-label="Between navigation regions" />
Accessibility: Passe decorative={false} e um aria-label quando o separador transmite estrutura (ex: entre regiões de navegação).

Skeleton

Placeholder animado exibido enquanto o conteúdo carrega. Use className para corresponder à forma do conteúdo real.

Linhas de texto

Card

Avatar + texto

import { Skeleton } from '@/components/ui/skeleton'

// Text lines
<div className="space-y-2">
  <Skeleton className="h-4 w-full" />
  <Skeleton className="h-4 w-5/6" />
</div>

// Avatar + text
<div className="flex items-center gap-3">
  <Skeleton className="size-10 rounded-full" />
  <div className="space-y-2">
    <Skeleton className="h-4 w-32" />
    <Skeleton className="h-3 w-20" />
  </div>
</div>

// Wrap loading region for screen readers
<div aria-busy="true" aria-label="Loading offers">
  <Skeleton className="h-48 w-full rounded-card" />
</div>
Accessibility: Envolva regiões de carregamento com aria-busy="true" e remova quando o conteúdo real aparecer. O Skeleton não carrega role ARIA.

Checkbox

Input binário para seleções booleanas. Construído sobre o Radix UI Checkbox — totalmente acessível por teclado.

States

import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'

// Default
<div className="flex items-center gap-2">
  <Checkbox id="terms" />
  <Label htmlFor="terms">Accept terms of service</Label>
</div>

// Controlled
const [checked, setChecked] = useState(false)
<Checkbox checked={checked} onCheckedChange={setChecked} />

// Indeterminate (e.g. select-all with partial selection)
<Checkbox checked="indeterminate" />
Accessibility: Sempre use com Label via htmlFor. O estado indeterminado usa checked="indeterminate" e requer um trigger programático — não é um estado HTML nativo.

Switch

Toggle para estados on/off. Prefira Switch ao Checkbox quando a ação tem efeito imediato (sem necessidade de submit).

Padrão (tamanhos sm + default)

Estados

import { Switch } from '@/components/ui/switch'
import { Label } from '@/components/ui/label'

// Uncontrolled
<div className="flex items-center gap-2">
  <Switch id="notifications" />
  <Label htmlFor="notifications">Email notifications</Label>
</div>

// Controlled
const [on, setOn] = useState(false)
<Switch checked={on} onCheckedChange={setOn} />

// Small size
<Switch size="sm" />
Accessibility: Sempre use com Label. O Switch comunica seu estado via aria-checked — nenhum ARIA adicional é necessário.

Tabs

Organiza conteúdo relacionado em painéis selecionáveis. Suporta duas variantes visuais: default (pílula) e line.

Padrão (pílula)

Descrição completa do serviço, inclusões, processo e prazo estimado aparecem aqui.

Linha

Descrição completa do serviço, inclusões, processo e prazo estimado aparecem aqui.

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'

// Default (pill) variant
<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="reviews">Reviews</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">Content A</TabsContent>
  <TabsContent value="reviews">Content B</TabsContent>
</Tabs>

// Line variant
<TabsList variant="line">
  <TabsTrigger value="overview">Overview</TabsTrigger>
</TabsList>
Accessibility: Teclado: teclas de seta movem o foco entre tabs, Enter/Espaço ativa. Os painéis são vinculados via aria-controls / aria-labelledby pelo Radix.

Tooltip

Label contextual não-interativa acionada em hover/foco. Envolva elementos interativos com TooltipProvider na raiz da árvore.

Padrão

import { Tooltip, TooltipTrigger, TooltipContent,
  TooltipProvider } from '@/components/ui/tooltip'

// Wrap the tree once (typically in a layout)
<TooltipProvider>
  <Tooltip>
    <TooltipTrigger asChild>
      <Button variant="ghost" size="icon" aria-label="Escrow info">
        <ShieldCheck className="size-4" />
      </Button>
    </TooltipTrigger>
    <TooltipContent>
      Funds held in escrow until delivery is confirmed
    </TooltipContent>
  </Tooltip>
</TooltipProvider>
Accessibility: O conteúdo do Tooltip não deve ser a única fonte de informação crítica — é invisível para usuários de teclado que não focam o trigger. Use TooltipProvider delayDuration={0} para remover o delay de abertura.

Dialog

Overlay modal que concentra atenção em uma decisão crítica ou formulário. Construído sobre o Radix UI Dialog — aprisiona foco, fecha com Escape.

Padrão

Ação destrutiva

import { Dialog, DialogTrigger, DialogContent, DialogHeader,
  DialogTitle, DialogDescription, DialogFooter
} from '@/components/ui/dialog'

<Dialog>
  <DialogTrigger asChild>
    <Button variant="outline">Open dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Confirm order</DialogTitle>
      <DialogDescription>
        Payment will be held in escrow until delivery.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button>Place order</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
Accessibility: DialogTitle e DialogDescription são obrigatórios — fornecem o nome acessível e a descrição consumidos por aria-labelledby / aria-describedby no container do dialog.

Callout

Mensagem contextual inline com ícone. Usada para destacar cues de confiança, notas de status e alertas não-bloqueantes sem interromper o fluxo do usuário.

Info (padrão)

O valor fica protegido em escrow até a conclusão aprovada. Ajustes e reembolsos seguem o Termo da Ordem.

Sucesso

Identidade verificada. Seu perfil está ativo e visível para tomadores.

Aviso

Esta oferta será pausada automaticamente após 30 dias de inatividade.

Perigo

Pagamento falhou. A transação não foi confirmada on-chain dentro do prazo.

Neutro

A disponibilidade exibida é indicativa. Confirme o horário diretamente com o prestador.
import { Callout } from '@/components/ui/callout'
import { ShieldCheck, AlertTriangle } from 'lucide-react'

// Info (default) — escrow trust cue
<Callout icon={<ShieldCheck />}>
  Your payment is protected by Solana escrow.
</Callout>

// Warning
<Callout variant="warning" icon={<AlertTriangle />}>
  This offer will be paused after 30 days of inactivity.
</Callout>

// Critical — add role="alert" for screen readers
<Callout variant="danger" icon={<AlertCircle />} role="alert">
  Payment failed. Please try again.
</Callout>
Accessibility: Callout é um container apresentacional — não carrega role ARIA por padrão. Se a mensagem for urgente ou crítica, envolva com role="alert" para que leitores de tela a anunciem imediatamente.

Pagination

Paginação baseada em offset com truncamento para grandes números de páginas. A página 1 omite o parâmetro page. O componente real usa useSearchParams para estado via URL — importe de @/components/public/pagination.

Padrão (página 2 de 8)

Primeira página (anterior desabilitado)

Última página (próxima desabilitada)

import { Pagination } from '@/components/public/pagination'

// URL-driven — reads/writes ?page= search param
<Suspense>
  <Pagination total={180} pageSize={20} page={currentPage} />
</Suspense>

Design System v1

Este é um documento vivo. Novos componentes e tokens serão adicionados conforme o produto evolui. Baixe os tokens para usá-los no Figma ou outras ferramentas.