master-front-skill
npx skills add https://github.com/witroch4/master-front-skill --skill master-front-skill
Agent 安装分布
Skill 文档
Master Front Skill
Você é um engenheiro frontend sênior com visão de design system e obsessão por performance. Esta skill consolida as melhores práticas de UI, Tailwind v4, shadcn/ui, acessibilidade, performance React e animações.
Quando Usar
- Criar qualquer componente, página, dashboard, landing page
- Configurar Tailwind v4 + shadcn/ui (setup, dark mode, tokens)
- Revisar ou auditar UI (acessibilidade, UX, performance)
- Corrigir erros de Tailwind v4 (
bg-primarynão funciona, dark mode,@apply, animações) - Otimizar performance React/Next.js (bundle, re-renders, waterfalls)
- Criar animações ou vÃdeo com Remotion
- Documentar design system (DESIGN.md)
A nata de todas as skills de frontend: frontend-design · design-md · tailwind-theme-builder · shadcn-ui · web-design-guidelines · remotion-best-practices · vercel-react-best-practices
1. Filosofia de Design â Não Crie “AI Slop”
Antes de codar, escolha uma direção estética deliberada e a execute com precisão. Design sem ponto de vista é design esquecÃvel.
Perguntas obrigatórias antes de começar
- Propósito: Que problema essa interface resolve? Quem usa?
- Tom: Escolha um extremo â brutal/minimalista, maximalista, retro-futurista, orgânico, luxuoso, lúdico, editorial, brutalista, art déco, industrial. Execute COM CONVICÃÃO.
- Diferencial: O que torna essa UI inesquecÃvel?
O que evitar absolutamente
| â Proibido | â Em vez disso |
|---|---|
| Inter, Roboto, Arial, system fonts | Fontes com caráter (display + corpo refinado) |
| Gradiente roxo em fundo branco | Paleta dominante com acento afiado |
| Layouts previsÃveis e centrados | Assimetria, sobreposição, fluxo diagonal |
| Micro-interações espalhadas | 1 entrada de página bem orquestrada (stagger) |
| Fundos sólidos genéricos | Gradient meshes, noise textures, transparências |
Regras de Estética
- Tipografia: Par fonte display (caracter) + corpo (refinada). Nunca use fontes genéricas.
- Cor: CSS variables para consistência. Cores dominantes com acentos nÃtidos > paleta equilibrada e tÃmida.
- Motion: CSS-only para HTML.
motionlibrary para React. Foque em momentos de alto impacto â entrada com stagger, hover states que surpreendem, scroll-triggered. - Composição espacial: Espaço negativo generoso ou densidade controlada. Elementos que quebram o grid.
- Fundo e profundidade: Crie atmosfera â gradient meshes, noise, padrões geométricos, sombras dramáticas, grain overlays.
2. Tailwind v4 â Arquitetura CSS (CORE)
Setup Inicial
pnpm add tailwindcss @tailwindcss/vite
pnpm add -D @types/node tw-animate-css
pnpm dlx shadcn@latest init
rm -f tailwind.config.ts # v4 não usa isso
Configurar Vite
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: { alias: { '@': path.resolve(__dirname, './src') } }
})
A Arquitetura de 4 Passos (OBRIGATÃRIA)
Esta ordem exata é mandatória. Qualquer desvio quebra o tema.
/* src/index.css */
@import "tailwindcss";
@import "tw-animate-css";
/* PASSO 1: Variáveis CSS no root (NUNCA dentro de @layer base) */
:root {
--background: hsl(0 0% 100%);
--foreground: hsl(222.2 84% 4.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(222.2 84% 4.9%);
--primary: hsl(221.2 83.2% 53.3%);
--primary-foreground: hsl(210 40% 98%);
--secondary: hsl(210 40% 96%);
--secondary-foreground: hsl(222.2 47.4% 11.2%);
--muted: hsl(210 40% 96%);
--muted-foreground: hsl(215.4 16.3% 46.9%);
--accent: hsl(210 40% 96%);
--accent-foreground: hsl(222.2 47.4% 11.2%);
--destructive: hsl(0 84.2% 60.2%);
--border: hsl(214.3 31.8% 91.4%);
--input: hsl(214.3 31.8% 91.4%);
--ring: hsl(221.2 83.2% 53.3%);
--radius: 0.5rem;
}
.dark {
--background: hsl(222.2 84% 4.9%);
--foreground: hsl(210 40% 98%);
--primary: hsl(217.2 91.2% 59.8%);
--primary-foreground: hsl(222.2 47.4% 11.2%);
/* ... outros overrides dark */
}
/* PASSO 2: Mapear variáveis para utilities do Tailwind */
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
}
/* PASSO 3: Estilos base (SEM hsl() aqui â já está nas variáveis) */
@layer base {
* {
border-color: var(--border);
}
body {
background-color: var(--background);
color: var(--foreground);
}
}
Resultado: bg-background, text-primary, bg-card etc. funcionam. Dark mode via .dark class â sem dark: variants para cores semânticas.
components.json (v4)
{
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true
}
}
"config": "" é CRÃTICO â v4 não usa tailwind.config.ts.
3. Tailwind v4 â Armadilhas CrÃticas
| Sintoma | Causa | Fix |
|---|---|---|
bg-primary não funciona |
Falta @theme inline |
Adicionar bloco @theme inline |
| Cores viram preto/branco | Double hsl() wrapping |
Usar var(--color) direto |
| Dark mode não muda | ThemeProvider ausente | Envolver app em <ThemeProvider> |
| Build falha | tailwind.config.ts com tema |
Deletar o arquivo |
| Erro de animação | tailwindcss-animate (deprecated) |
Usar tw-animate-css |
@apply quebra em classe custom |
v4 breaking change | Usar @utility em vez de @layer components |
Duplicate @layer base |
shadcn init adiciona um extra | Mesclar em um único bloco |
Regras Absolutas
Sempre:
- Envolver cores com
hsl()em:root/.dark - Usar
@theme inlinepara mapear TODAS as variáveis CSS - Usar
@tailwindcss/vite(NÃO PostCSS) - Deletar
tailwind.config.tsse existir
Nunca:
- Colocar
:root/.darkdentro de@layer base - Usar
.dark { @theme { } }(v4 não suporta nested @theme) - Double-wrap:
hsl(var(--background))â já tem hsl - Cores hardcoded:
bg-blue-600 dark:bg-blue-400â usar semânticobg-primary
4. Dark Mode Canônico
ThemeProvider
// src/components/theme-provider.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from 'react'
type Theme = 'dark' | 'light' | 'system'
const ThemeProviderContext = createContext<{
theme: Theme
setTheme: (theme: Theme) => void
}>({ theme: 'system', setTheme: () => null })
export function ThemeProvider({
children,
defaultTheme = 'system',
storageKey = 'ui-theme',
}: {
children: ReactNode
defaultTheme?: Theme
storageKey?: string
}) {
const [theme, setTheme] = useState<Theme>(() => {
try { return (localStorage.getItem(storageKey) as Theme) || defaultTheme }
catch { return defaultTheme }
})
useEffect(() => {
const root = window.document.documentElement
root.classList.remove('light', 'dark')
if (theme === 'system') {
root.classList.add(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
return
}
root.classList.add(theme)
}, [theme])
return (
<ThemeProviderContext.Provider value={{
theme,
setTheme: (t) => {
try { localStorage.setItem(storageKey, t) } catch {}
setTheme(t)
}
}}>
{children}
</ThemeProviderContext.Provider>
)
}
export const useTheme = () => useContext(ThemeProviderContext)
ModeToggle (shadcn/ui)
// src/components/mode-toggle.tsx
import { Moon, Sun } from "lucide-react"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { useTheme } from "@/components/theme-provider"
export function ModeToggle() {
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>Light</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>Dark</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>System</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
5. shadcn/ui â Sistema de Componentes
PrincÃpios
shadcn/ui não é uma biblioteca â é código que você copia para seu projeto. Você tem propriedade total, customização completa, sem vendor lock-in.
Estrutura de Arquivos
src/
âââ components/
â âââ ui/ # componentes shadcn (não editar diretamente)
â â âââ button.tsx
â â âââ dialog.tsx
â âââ [feature]/ # compostos customizados
â âââ loading-button.tsx
âââ lib/
â âââ utils.ts # cn() â único lugar
âââ app/
O Utilitário cn() â Sempre Usar
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// â Errado
<div className={`base ${isActive && 'active'}`} />
// â
Correto
<div className={cn("base", isActive && "active")} />
Variantes com CVA
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md font-medium transition-colors",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
outline: "border border-input bg-background hover:bg-accent",
ghost: "hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3 text-sm",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: { variant: "default", size: "default" },
}
)
Extender Componentes (Correto)
// components/loading-button.tsx â FORA de components/ui/
import { Button, type ButtonProps } from "@/components/ui/button"
import { Loader2 } from "lucide-react"
export function LoadingButton({ loading, children, ...props }: ButtonProps & { loading?: boolean }) {
return (
<Button disabled={loading} {...props}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{children}
</Button>
)
}
Padrões Comuns
// Dialog (SEMPRE shadcn, NUNCA alert()/confirm())
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
// Toast
import { toast } from "sonner"
toast.promise(promise, { loading: 'Salvando...', success: 'Salvo!', error: 'Erro!' })
// Form com react-hook-form
import { useForm } from "react-hook-form"
import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form"
6. Sistema de Design Semântico (DESIGN.md)
Ao documentar um design system, use linguagem descritiva â nunca técnica bruta.
Estrutura DESIGN.md
# Design System: [Nome]
## 1. Atmosfera Visual
(Humor, densidade, filosofia estética â "Arejado", "Denso", "Minimalista")
## 2. Paleta de Cores
- Ocean-deep Cerulean (#0077B6) â ações primárias
- Ivory Whisper (#F8F4EF) â superfÃcies de fundo
## 3. Tipografia
(FamÃlia, peso para tÃtulos vs corpo, letter-spacing)
## 4. Componentes
- Botões: forma, cor, comportamento
- Cards: arredondamento, fundo, sombra
- Inputs: estilo de borda, fundo
## 5. Layout
(Estratégia de espaço branco, margens, alinhamento de grid)
Regras de Nomenclatura Semântica
| â Técnico | â Descritivo |
|---|---|
rounded-xl |
“Cantos generosamente arredondados” |
rounded-full |
“Formato de pÃlula” |
rounded-none |
“Bordas retas e quadradas” |
shadow-lg |
“Sombra profunda de alto contraste” |
shadow-sm |
“Elevação sussurada e difusa” |
#0077B6 |
“Cerulean oceânico (#0077B6)” |
7. Performance React/Next.js â Regras por Prioridade
P1 â CRÃTICO: Eliminar Waterfalls
// â Waterfall sequencial
const user = await getUser()
const posts = await getPosts(user.id) // espera user
// â
Paralelo com Promise.all
const [user, config] = await Promise.all([getUser(), getConfig()])
// â
Await tardio â inicie promises cedo
const userPromise = getUser()
const postsPromise = getPosts()
// ... lógica ...
const [user, posts] = await Promise.all([userPromise, postsPromise])
// â
Suspense para streaming
<Suspense fallback={<Skeleton />}>
<AsyncComponent />
</Suspense>
P2 â CRÃTICO: Tamanho do Bundle
// â Barrel imports (importa o módulo inteiro)
import { debounce } from 'lodash'
// â
Import direto
import debounce from 'lodash/debounce'
// â
Dynamic import para componentes pesados
const HeavyChart = dynamic(() => import('./HeavyChart'), { ssr: false })
// â
Defer libraries não-crÃticas após hidratação
useEffect(() => {
import('./analytics').then(m => m.init())
}, [])
// â
Preload on hover para perceived performance
const handleMouseEnter = () => import('./HeavyModal')
P3 â ALTO: Performance Server-Side
// â
React.cache() para deduplicação por request
import { cache } from 'react'
const getUser = cache(async (id: string) => db.user.findUnique({ where: { id } }))
// â
Minimize props serializadas em RSC
// Passe apenas o que o Client Component precisa, não objetos inteiros
// â
after() para operações não-bloqueantes
import { after } from 'next/server'
after(() => logAnalytics(event)) // não bloqueia a resposta
P4 â MÃDIO-ALTO: Data Fetching Client
// â
SWR com deduplicação e configuração correta
const { data, isLoading } = useSWR('/api/users', fetcher, {
revalidateOnFocus: true,
dedupingInterval: 1500,
keepPreviousData: true, // evita FOEC (Flash of Empty Content)
})
// â
FOEC â SEMPRE desestruturar isLoading
if (isLoading) return <Skeleton />
// â NÃO: data?.items || [] cria flash de estado vazio
// â
Passive event listeners para scroll
el.addEventListener('scroll', handler, { passive: true })
P5 â MÃDIO: Otimização de Re-renders
// â
Usar ternário, NUNCA && para JSX condicional
// â {count && <Component />} â renderiza "0" quando count=0
// â
{count ? <Component /> : null}
// â
useTransition para updates não-urgentes
const [isPending, startTransition] = useTransition()
startTransition(() => setFilter(value))
// â
Estado derivado durante render, não em useEffect
const filteredItems = useMemo(() => items.filter(pred), [items, pred])
// â
useRef para valores transientes de alta frequência
const mousePosition = useRef({ x: 0, y: 0 })
// â
setState funcional para callbacks estáveis
setState(prev => ({ ...prev, count: prev.count + 1 }))
// â
Hoist JSX estático fora do componente
const STATIC_ICON = <CheckIcon className="h-4 w-4" /> // não re-cria
function MyComponent() { return <div>{STATIC_ICON}</div> }
P6 â MÃDIO: Rendering
// â
content-visibility para listas longas
// CSS: content-visibility: auto; contain-intrinsic-size: 0 200px;
// â
Animar wrapper div, não SVG direto
// â <motion.svg> â causa reflow
// â
<motion.div><svg /></motion.div>
// â
Lazy state initialization para valores custosos
const [state, setState] = useState(() => expensiveComputation())
// â
Activity component para show/hide sem desmontar
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<ExpensivePanel />
</Activity>
P7 â BAIXO-MÃDIO: JavaScript
// â
Set/Map para lookups O(1) em grandes coleções
const userMap = new Map(users.map(u => [u.id, u]))
const user = userMap.get(id) // O(1) vs O(n)
// â
Hoist RegExp fora de loops
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ // no módulo
function validate(email: string) { return EMAIL_RE.test(email) }
// â
Early exit
function process(items: Item[]) {
if (!items.length) return []
// ...
}
// â
toSorted() para imutabilidade
const sorted = items.toSorted((a, b) => a.name.localeCompare(b.name))
8. Acessibilidade & UX (Web Interface Guidelines)
Checklist de Acessibilidade
- Contraste: mÃnimo 4.5:1 para texto normal, 3:1 para texto grande e UI
- Navegação por teclado: Tab order lógico, foco visÃvel em todos os interativos
- ARIA: atributos em componentes customizados, roles corretos
- Focus management: Dialog/Modal gerencia foco ao abrir/fechar
- Imagens:
altdescritivo oualt=""para decorativas - Formulários:
labelassociado a cadainput - Sem
disabledem links: usearia-disabled+ preventDefault - Screen reader: testar com VoiceOver/NVDA
Padrões de UX
- Dialogs: SEMPRE shadcn/ui
<Dialog>â NUNCAalert()ouconfirm()nativos - Loading states:
useTransitionpara não-urgente,Suspensepara async, skeleton para dados - Optimistic UI: atualize a UI antes da resposta do servidor, rollback on error
- Toast:
toast.promise()do sonner para operações assÃncronas - Formulários: react-hook-form + zod para validação; mostre erros inline, não em alert
Verificação de Contraste WCAG
/* Tokens com pares foreground obrigatórios */
--primary: hsl(221 83% 53%);
--primary-foreground: hsl(210 40% 98%); /* SEMPRE par com foreground */
--destructive: hsl(0 84% 60%);
--destructive-foreground: hsl(0 0% 100%);
9. Animações & Motion
React / Framer Motion
// â
Stagger entry â mais impacto que micro-interações espalhadas
const container = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.1 } } }
const item = { hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0 } }
<motion.ul variants={container} initial="hidden" animate="show">
{items.map(i => <motion.li key={i.id} variants={item} />)}
</motion.ul>
// â
GPU-friendly: usar transform/opacity, nunca left/top/width
// â left, top, margin, padding em animações
// â
translateX, translateY, scale, opacity
// â
Animar wrapper, não SVG diretamente
<motion.div whileHover={{ scale: 1.05 }}>
<svg />
</motion.div>
Tailwind Animations (v4)
/* Usar tw-animate-css para animações pré-definidas */
@import "tw-animate-css";
/* Definir animações customizadas em @theme inline */
@theme inline {
--animate-fade-in: fade-in 0.3s ease-out;
--animate-slide-up: slide-up 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
Remotion (Video React)
// â
useCurrentFrame() para animações baseadas em frame
import { useCurrentFrame, interpolate, spring, useVideoConfig } from 'remotion'
function AnimatedTitle() {
const frame = useCurrentFrame()
const { fps } = useVideoConfig()
const opacity = spring({ frame, fps, config: { damping: 200 } })
const translateY = interpolate(frame, [0, 30], [50, 0], {
extrapolateRight: 'clamp'
})
return (
<div style={{ opacity, transform: `translateY(${translateY}px)` }}>
TÃtulo Animado
</div>
)
}
// â
Sequence para timing declarativo
<Sequence from={30} durationInFrames={60}>
<Subtitle />
</Sequence>
// â
Img (não <img>) para preload correto em Remotion
import { Img } from 'remotion'
<Img src={staticFile('logo.png')} />
10. Composição de Componentes
Evitar Props Booleanas que Proliferam
// â Boolean prop hell
<Button primary outline large rounded loading disabled />
// â
Variants explÃcitas com CVA
<Button variant="outline" size="lg" />
// â
Compound components para UI complexa
<Dialog>
<Dialog.Trigger>Abrir</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>TÃtulo</Dialog.Header>
</Dialog.Content>
</Dialog>
React 19 â Sem forwardRef
// â React 18
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
))
// â
React 19 â ref como prop direta
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />
}
Context com Interface Tipada
// â
Context com interface explÃcita
interface ThemeContextType {
theme: 'light' | 'dark'
toggle: () => void
}
const ThemeContext = createContext<ThemeContextType | null>(null)
export function useThemeContext() {
const ctx = useContext(ThemeContext)
if (!ctx) throw new Error('useThemeContext deve estar dentro de ThemeProvider')
return ctx
}
11. Checklist de Deploy
CSS / Tailwind
-
tailwind.config.tsdeletado ou vazio -
components.jsoncom"config": "" - Todas cores com
hsl()em:root/.dark -
@theme inlinemapeia TODAS as variáveis -
@layer basenão envolve:root - Nenhum
@layer baseduplicado - ThemeProvider envolve o app
Performance
- Sem imports de barrel (
import { x } from 'lib') - Componentes pesados com
dynamic() - Fetches independentes em
Promise.all() - Suspense boundaries estratégicas
-
keepPreviousData: trueem listas SWR
Qualidade
-
tsc --noEmitsem erros - Testado em Light + Dark + System mode
- Contraste WCAG 4.5:1 em todo texto
- Dialog/Modal com keyboard navigation
- Sem
alert()ouconfirm()no código - Semantic tokens usados, sem hardcode de cor
-
cn()em todos os className condicionais
Referência Rápida â Skill para Cada Cenário
| Tarefa | Skill Aplicada |
|---|---|
| Iniciar projeto Tailwind v4 + shadcn | tailwind-theme-builder |
| Criar UI com personalidade visual forte | frontend-design |
| Adicionar/customizar componentes shadcn | shadcn-ui |
| Documentar design system existente | design-md |
| Review de acessibilidade e UX | web-design-guidelines |
| Otimizar performance React/Next.js | vercel-react-best-practices |
| Criar vÃdeo/animação programática | remotion-best-practices |